##// END OF EJS Templates
Makes .find_ids return integers....
Jean-Philippe Lang -
r8370:86a52eaedf2b
parent child
Show More
@@ -1,351 +1,351
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => [:new, :create]
19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :update]
22 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
24 before_filter :check_project_uniqueness, :only => [:move, :perform_move]
24 before_filter :check_project_uniqueness, :only => [:move, :perform_move]
25 before_filter :find_project, :only => [:new, :create]
25 before_filter :find_project, :only => [:new, :create]
26 before_filter :authorize, :except => [:index]
26 before_filter :authorize, :except => [:index]
27 before_filter :find_optional_project, :only => [:index]
27 before_filter :find_optional_project, :only => [:index]
28 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :check_for_default_issue_status, :only => [:new, :create]
29 before_filter :build_new_issue_from_params, :only => [:new, :create]
29 before_filter :build_new_issue_from_params, :only => [:new, :create]
30 accept_rss_auth :index, :show
30 accept_rss_auth :index, :show
31 accept_api_auth :index, :show, :create, :update, :destroy
31 accept_api_auth :index, :show, :create, :update, :destroy
32
32
33 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
34
34
35 helper :journals
35 helper :journals
36 helper :projects
36 helper :projects
37 include ProjectsHelper
37 include ProjectsHelper
38 helper :custom_fields
38 helper :custom_fields
39 include CustomFieldsHelper
39 include CustomFieldsHelper
40 helper :issue_relations
40 helper :issue_relations
41 include IssueRelationsHelper
41 include IssueRelationsHelper
42 helper :watchers
42 helper :watchers
43 include WatchersHelper
43 include WatchersHelper
44 helper :attachments
44 helper :attachments
45 include AttachmentsHelper
45 include AttachmentsHelper
46 helper :queries
46 helper :queries
47 include QueriesHelper
47 include QueriesHelper
48 helper :repositories
48 helper :repositories
49 include RepositoriesHelper
49 include RepositoriesHelper
50 helper :sort
50 helper :sort
51 include SortHelper
51 include SortHelper
52 include IssuesHelper
52 include IssuesHelper
53 helper :timelog
53 helper :timelog
54 helper :gantt
54 helper :gantt
55 include Redmine::Export::PDF
55 include Redmine::Export::PDF
56
56
57 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
57 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
58 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
58 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
59 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
59 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
60
60
61 def index
61 def index
62 retrieve_query
62 retrieve_query
63 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
63 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
64 sort_update(@query.sortable_columns)
64 sort_update(@query.sortable_columns)
65
65
66 if @query.valid?
66 if @query.valid?
67 case params[:format]
67 case params[:format]
68 when 'csv', 'pdf'
68 when 'csv', 'pdf'
69 @limit = Setting.issues_export_limit.to_i
69 @limit = Setting.issues_export_limit.to_i
70 when 'atom'
70 when 'atom'
71 @limit = Setting.feeds_limit.to_i
71 @limit = Setting.feeds_limit.to_i
72 when 'xml', 'json'
72 when 'xml', 'json'
73 @offset, @limit = api_offset_and_limit
73 @offset, @limit = api_offset_and_limit
74 else
74 else
75 @limit = per_page_option
75 @limit = per_page_option
76 end
76 end
77
77
78 @issue_count = @query.issue_count
78 @issue_count = @query.issue_count
79 @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
79 @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
80 @offset ||= @issue_pages.current.offset
80 @offset ||= @issue_pages.current.offset
81 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
81 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
82 :order => sort_clause,
82 :order => sort_clause,
83 :offset => @offset,
83 :offset => @offset,
84 :limit => @limit)
84 :limit => @limit)
85 @issue_count_by_group = @query.issue_count_by_group
85 @issue_count_by_group = @query.issue_count_by_group
86
86
87 respond_to do |format|
87 respond_to do |format|
88 format.html { render :template => 'issues/index', :layout => !request.xhr? }
88 format.html { render :template => 'issues/index', :layout => !request.xhr? }
89 format.api {
89 format.api {
90 Issue.load_relations(@issues) if include_in_api_response?('relations')
90 Issue.load_relations(@issues) if include_in_api_response?('relations')
91 }
91 }
92 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
92 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
93 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
93 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
94 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
94 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
95 end
95 end
96 else
96 else
97 respond_to do |format|
97 respond_to do |format|
98 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
98 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
99 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
99 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
100 format.api { render_validation_errors(@query) }
100 format.api { render_validation_errors(@query) }
101 end
101 end
102 end
102 end
103 rescue ActiveRecord::RecordNotFound
103 rescue ActiveRecord::RecordNotFound
104 render_404
104 render_404
105 end
105 end
106
106
107 def show
107 def show
108 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
108 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
109 @journals.each_with_index {|j,i| j.indice = i+1}
109 @journals.each_with_index {|j,i| j.indice = i+1}
110 @journals.reverse! if User.current.wants_comments_in_reverse_order?
110 @journals.reverse! if User.current.wants_comments_in_reverse_order?
111
111
112 if User.current.allowed_to?(:view_changesets, @project)
112 if User.current.allowed_to?(:view_changesets, @project)
113 @changesets = @issue.changesets.visible.all
113 @changesets = @issue.changesets.visible.all
114 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
114 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
115 end
115 end
116
116
117 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
117 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
118 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
118 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
119 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
119 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
120 @priorities = IssuePriority.active
120 @priorities = IssuePriority.active
121 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
121 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
122 respond_to do |format|
122 respond_to do |format|
123 format.html {
123 format.html {
124 retrieve_previous_and_next_issue_ids
124 retrieve_previous_and_next_issue_ids
125 render :template => 'issues/show'
125 render :template => 'issues/show'
126 }
126 }
127 format.api
127 format.api
128 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
128 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
129 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
129 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
130 end
130 end
131 end
131 end
132
132
133 # Add a new issue
133 # Add a new issue
134 # The new issue will be created from an existing one if copy_from parameter is given
134 # The new issue will be created from an existing one if copy_from parameter is given
135 def new
135 def new
136 respond_to do |format|
136 respond_to do |format|
137 format.html { render :action => 'new', :layout => !request.xhr? }
137 format.html { render :action => 'new', :layout => !request.xhr? }
138 format.js { render :partial => 'attributes' }
138 format.js { render :partial => 'attributes' }
139 end
139 end
140 end
140 end
141
141
142 def create
142 def create
143 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
143 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
144 if @issue.save
144 if @issue.save
145 attachments = Attachment.attach_files(@issue, params[:attachments])
145 attachments = Attachment.attach_files(@issue, params[:attachments])
146 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
146 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
147 respond_to do |format|
147 respond_to do |format|
148 format.html {
148 format.html {
149 render_attachment_warning_if_needed(@issue)
149 render_attachment_warning_if_needed(@issue)
150 flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
150 flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
151 redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
151 redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
152 { :action => 'show', :id => @issue })
152 { :action => 'show', :id => @issue })
153 }
153 }
154 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
154 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
155 end
155 end
156 return
156 return
157 else
157 else
158 respond_to do |format|
158 respond_to do |format|
159 format.html { render :action => 'new' }
159 format.html { render :action => 'new' }
160 format.api { render_validation_errors(@issue) }
160 format.api { render_validation_errors(@issue) }
161 end
161 end
162 end
162 end
163 end
163 end
164
164
165 def edit
165 def edit
166 update_issue_from_params
166 update_issue_from_params
167
167
168 @journal = @issue.current_journal
168 @journal = @issue.current_journal
169
169
170 respond_to do |format|
170 respond_to do |format|
171 format.html { }
171 format.html { }
172 format.xml { }
172 format.xml { }
173 end
173 end
174 end
174 end
175
175
176 def update
176 def update
177 update_issue_from_params
177 update_issue_from_params
178
178
179 if @issue.save_issue_with_child_records(params, @time_entry)
179 if @issue.save_issue_with_child_records(params, @time_entry)
180 render_attachment_warning_if_needed(@issue)
180 render_attachment_warning_if_needed(@issue)
181 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
181 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
182
182
183 respond_to do |format|
183 respond_to do |format|
184 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
184 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
185 format.api { head :ok }
185 format.api { head :ok }
186 end
186 end
187 else
187 else
188 render_attachment_warning_if_needed(@issue)
188 render_attachment_warning_if_needed(@issue)
189 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
189 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
190 @journal = @issue.current_journal
190 @journal = @issue.current_journal
191
191
192 respond_to do |format|
192 respond_to do |format|
193 format.html { render :action => 'edit' }
193 format.html { render :action => 'edit' }
194 format.api { render_validation_errors(@issue) }
194 format.api { render_validation_errors(@issue) }
195 end
195 end
196 end
196 end
197 end
197 end
198
198
199 # Bulk edit a set of issues
199 # Bulk edit a set of issues
200 def bulk_edit
200 def bulk_edit
201 @issues.sort!
201 @issues.sort!
202 @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
202 @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
203 @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
203 @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
204 @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
204 @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
205 @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
205 @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
206 end
206 end
207
207
208 def bulk_update
208 def bulk_update
209 @issues.sort!
209 @issues.sort!
210 attributes = parse_params_for_bulk_issue_attributes(params)
210 attributes = parse_params_for_bulk_issue_attributes(params)
211
211
212 unsaved_issue_ids = []
212 unsaved_issue_ids = []
213 @issues.each do |issue|
213 @issues.each do |issue|
214 issue.reload
214 issue.reload
215 journal = issue.init_journal(User.current, params[:notes])
215 journal = issue.init_journal(User.current, params[:notes])
216 issue.safe_attributes = attributes
216 issue.safe_attributes = attributes
217 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
217 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
218 unless issue.save
218 unless issue.save
219 # Keep unsaved issue ids to display them in flash error
219 # Keep unsaved issue ids to display them in flash error
220 unsaved_issue_ids << issue.id
220 unsaved_issue_ids << issue.id
221 end
221 end
222 end
222 end
223 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
223 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
224 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
224 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
225 end
225 end
226
226
227 verify :method => :delete, :only => :destroy, :render => { :nothing => true, :status => :method_not_allowed }
227 verify :method => :delete, :only => :destroy, :render => { :nothing => true, :status => :method_not_allowed }
228 def destroy
228 def destroy
229 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
229 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
230 if @hours > 0
230 if @hours > 0
231 case params[:todo]
231 case params[:todo]
232 when 'destroy'
232 when 'destroy'
233 # nothing to do
233 # nothing to do
234 when 'nullify'
234 when 'nullify'
235 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
235 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
236 when 'reassign'
236 when 'reassign'
237 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
237 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
238 if reassign_to.nil?
238 if reassign_to.nil?
239 flash.now[:error] = l(:error_issue_not_found_in_project)
239 flash.now[:error] = l(:error_issue_not_found_in_project)
240 return
240 return
241 else
241 else
242 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
242 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
243 end
243 end
244 else
244 else
245 # display the destroy form if it's a user request
245 # display the destroy form if it's a user request
246 return unless api_request?
246 return unless api_request?
247 end
247 end
248 end
248 end
249 @issues.each do |issue|
249 @issues.each do |issue|
250 begin
250 begin
251 issue.reload.destroy
251 issue.reload.destroy
252 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
252 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
253 # nothing to do, issue was already deleted (eg. by a parent)
253 # nothing to do, issue was already deleted (eg. by a parent)
254 end
254 end
255 end
255 end
256 respond_to do |format|
256 respond_to do |format|
257 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
257 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
258 format.api { head :ok }
258 format.api { head :ok }
259 end
259 end
260 end
260 end
261
261
262 private
262 private
263 def find_issue
263 def find_issue
264 # Issue.visible.find(...) can not be used to redirect user to the login form
264 # Issue.visible.find(...) can not be used to redirect user to the login form
265 # if the issue actually exists but requires authentication
265 # if the issue actually exists but requires authentication
266 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
266 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
267 unless @issue.visible?
267 unless @issue.visible?
268 deny_access
268 deny_access
269 return
269 return
270 end
270 end
271 @project = @issue.project
271 @project = @issue.project
272 rescue ActiveRecord::RecordNotFound
272 rescue ActiveRecord::RecordNotFound
273 render_404
273 render_404
274 end
274 end
275
275
276 def find_project
276 def find_project
277 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
277 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
278 @project = Project.find(project_id)
278 @project = Project.find(project_id)
279 rescue ActiveRecord::RecordNotFound
279 rescue ActiveRecord::RecordNotFound
280 render_404
280 render_404
281 end
281 end
282
282
283 def retrieve_previous_and_next_issue_ids
283 def retrieve_previous_and_next_issue_ids
284 retrieve_query_from_session
284 retrieve_query_from_session
285 if @query
285 if @query
286 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
286 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
287 sort_update(@query.sortable_columns, 'issues_index_sort')
287 sort_update(@query.sortable_columns, 'issues_index_sort')
288 limit = 500
288 limit = 500
289 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1))
289 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1))
290 if (idx = issue_ids.index(@issue.id.to_s)) && idx < limit
290 if (idx = issue_ids.index(@issue.id)) && idx < limit
291 @prev_issue_id = issue_ids[idx - 1].to_i if idx > 0
291 @prev_issue_id = issue_ids[idx - 1] if idx > 0
292 @next_issue_id = issue_ids[idx + 1].to_i if idx < (issue_ids.size - 1)
292 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
293 end
293 end
294 end
294 end
295 end
295 end
296
296
297 # Used by #edit and #update to set some common instance variables
297 # Used by #edit and #update to set some common instance variables
298 # from the params
298 # from the params
299 # TODO: Refactor, not everything in here is needed by #edit
299 # TODO: Refactor, not everything in here is needed by #edit
300 def update_issue_from_params
300 def update_issue_from_params
301 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
301 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
302 @priorities = IssuePriority.active
302 @priorities = IssuePriority.active
303 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
303 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
304 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
304 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
305 @time_entry.attributes = params[:time_entry]
305 @time_entry.attributes = params[:time_entry]
306
306
307 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
307 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
308 @issue.init_journal(User.current, @notes)
308 @issue.init_journal(User.current, @notes)
309 @issue.safe_attributes = params[:issue]
309 @issue.safe_attributes = params[:issue]
310 end
310 end
311
311
312 # TODO: Refactor, lots of extra code in here
312 # TODO: Refactor, lots of extra code in here
313 # TODO: Changing tracker on an existing issue should not trigger this
313 # TODO: Changing tracker on an existing issue should not trigger this
314 def build_new_issue_from_params
314 def build_new_issue_from_params
315 if params[:id].blank?
315 if params[:id].blank?
316 @issue = Issue.new
316 @issue = Issue.new
317 @issue.copy_from(params[:copy_from]) if params[:copy_from]
317 @issue.copy_from(params[:copy_from]) if params[:copy_from]
318 @issue.project = @project
318 @issue.project = @project
319 else
319 else
320 @issue = @project.issues.visible.find(params[:id])
320 @issue = @project.issues.visible.find(params[:id])
321 end
321 end
322
322
323 @issue.project = @project
323 @issue.project = @project
324 @issue.author = User.current
324 @issue.author = User.current
325 # Tracker must be set before custom field values
325 # Tracker must be set before custom field values
326 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
326 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
327 if @issue.tracker.nil?
327 if @issue.tracker.nil?
328 render_error l(:error_no_tracker_in_project)
328 render_error l(:error_no_tracker_in_project)
329 return false
329 return false
330 end
330 end
331 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
331 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
332 @issue.safe_attributes = params[:issue]
332 @issue.safe_attributes = params[:issue]
333
333
334 @priorities = IssuePriority.active
334 @priorities = IssuePriority.active
335 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
335 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
336 end
336 end
337
337
338 def check_for_default_issue_status
338 def check_for_default_issue_status
339 if IssueStatus.default.nil?
339 if IssueStatus.default.nil?
340 render_error l(:error_no_default_issue_status)
340 render_error l(:error_no_default_issue_status)
341 return false
341 return false
342 end
342 end
343 end
343 end
344
344
345 def parse_params_for_bulk_issue_attributes(params)
345 def parse_params_for_bulk_issue_attributes(params)
346 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
346 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
347 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
347 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
348 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
348 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
349 attributes
349 attributes
350 end
350 end
351 end
351 end
@@ -1,53 +1,53
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module ActiveRecord
18 module ActiveRecord
19 class Base
19 class Base
20 def self.find_ids(options={})
20 def self.find_ids(options={})
21 find_ids_with_associations(options)
21 find_ids_with_associations(options)
22 end
22 end
23 end
23 end
24
24
25 module Associations
25 module Associations
26 module ClassMethods
26 module ClassMethods
27 def find_ids_with_associations(options = {})
27 def find_ids_with_associations(options = {})
28 catch :invalid_query do
28 catch :invalid_query do
29 join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
29 join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
30 return connection.select_values(construct_ids_finder_sql_with_included_associations(options, join_dependency))
30 return connection.select_values(construct_ids_finder_sql_with_included_associations(options, join_dependency)).map(&:to_i)
31 end
31 end
32 []
32 []
33 end
33 end
34
34
35 def construct_ids_finder_sql_with_included_associations(options, join_dependency)
35 def construct_ids_finder_sql_with_included_associations(options, join_dependency)
36 scope = scope(:find)
36 scope = scope(:find)
37 sql = "SELECT #{table_name}.id FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
37 sql = "SELECT #{table_name}.id FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
38 sql << join_dependency.join_associations.collect{|join| join.association_join }.join
38 sql << join_dependency.join_associations.collect{|join| join.association_join }.join
39
39
40 add_joins!(sql, options[:joins], scope)
40 add_joins!(sql, options[:joins], scope)
41 add_conditions!(sql, options[:conditions], scope)
41 add_conditions!(sql, options[:conditions], scope)
42 add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
42 add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
43
43
44 add_group!(sql, options[:group], options[:having], scope)
44 add_group!(sql, options[:group], options[:having], scope)
45 add_order!(sql, options[:order], scope)
45 add_order!(sql, options[:order], scope)
46 add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
46 add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
47 add_lock!(sql, options, scope)
47 add_lock!(sql, options, scope)
48
48
49 return sanitize_sql(sql)
49 return sanitize_sql(sql)
50 end
50 end
51 end
51 end
52 end
52 end
53 end
53 end
@@ -1,869 +1,869
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class QueryTest < ActiveSupport::TestCase
20 class QueryTest < ActiveSupport::TestCase
21 fixtures :projects, :enabled_modules, :users, :members,
21 fixtures :projects, :enabled_modules, :users, :members,
22 :member_roles, :roles, :trackers, :issue_statuses,
22 :member_roles, :roles, :trackers, :issue_statuses,
23 :issue_categories, :enumerations, :issues,
23 :issue_categories, :enumerations, :issues,
24 :watchers, :custom_fields, :custom_values, :versions,
24 :watchers, :custom_fields, :custom_values, :versions,
25 :queries,
25 :queries,
26 :projects_trackers
26 :projects_trackers
27
27
28 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
28 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
29 query = Query.new(:project => nil, :name => '_')
29 query = Query.new(:project => nil, :name => '_')
30 assert query.available_filters.has_key?('cf_1')
30 assert query.available_filters.has_key?('cf_1')
31 assert !query.available_filters.has_key?('cf_3')
31 assert !query.available_filters.has_key?('cf_3')
32 end
32 end
33
33
34 def test_system_shared_versions_should_be_available_in_global_queries
34 def test_system_shared_versions_should_be_available_in_global_queries
35 Version.find(2).update_attribute :sharing, 'system'
35 Version.find(2).update_attribute :sharing, 'system'
36 query = Query.new(:project => nil, :name => '_')
36 query = Query.new(:project => nil, :name => '_')
37 assert query.available_filters.has_key?('fixed_version_id')
37 assert query.available_filters.has_key?('fixed_version_id')
38 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
38 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
39 end
39 end
40
40
41 def test_project_filter_in_global_queries
41 def test_project_filter_in_global_queries
42 query = Query.new(:project => nil, :name => '_')
42 query = Query.new(:project => nil, :name => '_')
43 project_filter = query.available_filters["project_id"]
43 project_filter = query.available_filters["project_id"]
44 assert_not_nil project_filter
44 assert_not_nil project_filter
45 project_ids = project_filter[:values].map{|p| p[1]}
45 project_ids = project_filter[:values].map{|p| p[1]}
46 assert project_ids.include?("1") #public project
46 assert project_ids.include?("1") #public project
47 assert !project_ids.include?("2") #private project user cannot see
47 assert !project_ids.include?("2") #private project user cannot see
48 end
48 end
49
49
50 def find_issues_with_query(query)
50 def find_issues_with_query(query)
51 Issue.find :all,
51 Issue.find :all,
52 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
52 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
53 :conditions => query.statement
53 :conditions => query.statement
54 end
54 end
55
55
56 def assert_find_issues_with_query_is_successful(query)
56 def assert_find_issues_with_query_is_successful(query)
57 assert_nothing_raised do
57 assert_nothing_raised do
58 find_issues_with_query(query)
58 find_issues_with_query(query)
59 end
59 end
60 end
60 end
61
61
62 def assert_query_statement_includes(query, condition)
62 def assert_query_statement_includes(query, condition)
63 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
63 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
64 end
64 end
65
65
66 def assert_query_result(expected, query)
66 def assert_query_result(expected, query)
67 assert_nothing_raised do
67 assert_nothing_raised do
68 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
68 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
69 assert_equal expected.size, query.issue_count
69 assert_equal expected.size, query.issue_count
70 end
70 end
71 end
71 end
72
72
73 def test_query_should_allow_shared_versions_for_a_project_query
73 def test_query_should_allow_shared_versions_for_a_project_query
74 subproject_version = Version.find(4)
74 subproject_version = Version.find(4)
75 query = Query.new(:project => Project.find(1), :name => '_')
75 query = Query.new(:project => Project.find(1), :name => '_')
76 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
76 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
77
77
78 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
78 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
79 end
79 end
80
80
81 def test_query_with_multiple_custom_fields
81 def test_query_with_multiple_custom_fields
82 query = Query.find(1)
82 query = Query.find(1)
83 assert query.valid?
83 assert query.valid?
84 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
84 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
85 issues = find_issues_with_query(query)
85 issues = find_issues_with_query(query)
86 assert_equal 1, issues.length
86 assert_equal 1, issues.length
87 assert_equal Issue.find(3), issues.first
87 assert_equal Issue.find(3), issues.first
88 end
88 end
89
89
90 def test_operator_none
90 def test_operator_none
91 query = Query.new(:project => Project.find(1), :name => '_')
91 query = Query.new(:project => Project.find(1), :name => '_')
92 query.add_filter('fixed_version_id', '!*', [''])
92 query.add_filter('fixed_version_id', '!*', [''])
93 query.add_filter('cf_1', '!*', [''])
93 query.add_filter('cf_1', '!*', [''])
94 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
94 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
95 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
95 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
96 find_issues_with_query(query)
96 find_issues_with_query(query)
97 end
97 end
98
98
99 def test_operator_none_for_integer
99 def test_operator_none_for_integer
100 query = Query.new(:project => Project.find(1), :name => '_')
100 query = Query.new(:project => Project.find(1), :name => '_')
101 query.add_filter('estimated_hours', '!*', [''])
101 query.add_filter('estimated_hours', '!*', [''])
102 issues = find_issues_with_query(query)
102 issues = find_issues_with_query(query)
103 assert !issues.empty?
103 assert !issues.empty?
104 assert issues.all? {|i| !i.estimated_hours}
104 assert issues.all? {|i| !i.estimated_hours}
105 end
105 end
106
106
107 def test_operator_none_for_date
107 def test_operator_none_for_date
108 query = Query.new(:project => Project.find(1), :name => '_')
108 query = Query.new(:project => Project.find(1), :name => '_')
109 query.add_filter('start_date', '!*', [''])
109 query.add_filter('start_date', '!*', [''])
110 issues = find_issues_with_query(query)
110 issues = find_issues_with_query(query)
111 assert !issues.empty?
111 assert !issues.empty?
112 assert issues.all? {|i| i.start_date.nil?}
112 assert issues.all? {|i| i.start_date.nil?}
113 end
113 end
114
114
115 def test_operator_all
115 def test_operator_all
116 query = Query.new(:project => Project.find(1), :name => '_')
116 query = Query.new(:project => Project.find(1), :name => '_')
117 query.add_filter('fixed_version_id', '*', [''])
117 query.add_filter('fixed_version_id', '*', [''])
118 query.add_filter('cf_1', '*', [''])
118 query.add_filter('cf_1', '*', [''])
119 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
119 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
120 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
120 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
121 find_issues_with_query(query)
121 find_issues_with_query(query)
122 end
122 end
123
123
124 def test_operator_all_for_date
124 def test_operator_all_for_date
125 query = Query.new(:project => Project.find(1), :name => '_')
125 query = Query.new(:project => Project.find(1), :name => '_')
126 query.add_filter('start_date', '*', [''])
126 query.add_filter('start_date', '*', [''])
127 issues = find_issues_with_query(query)
127 issues = find_issues_with_query(query)
128 assert !issues.empty?
128 assert !issues.empty?
129 assert issues.all? {|i| i.start_date.present?}
129 assert issues.all? {|i| i.start_date.present?}
130 end
130 end
131
131
132 def test_numeric_filter_should_not_accept_non_numeric_values
132 def test_numeric_filter_should_not_accept_non_numeric_values
133 query = Query.new(:name => '_')
133 query = Query.new(:name => '_')
134 query.add_filter('estimated_hours', '=', ['a'])
134 query.add_filter('estimated_hours', '=', ['a'])
135
135
136 assert query.has_filter?('estimated_hours')
136 assert query.has_filter?('estimated_hours')
137 assert !query.valid?
137 assert !query.valid?
138 end
138 end
139
139
140 def test_operator_is_on_float
140 def test_operator_is_on_float
141 Issue.update_all("estimated_hours = 171.2", "id=2")
141 Issue.update_all("estimated_hours = 171.2", "id=2")
142
142
143 query = Query.new(:name => '_')
143 query = Query.new(:name => '_')
144 query.add_filter('estimated_hours', '=', ['171.20'])
144 query.add_filter('estimated_hours', '=', ['171.20'])
145 issues = find_issues_with_query(query)
145 issues = find_issues_with_query(query)
146 assert_equal 1, issues.size
146 assert_equal 1, issues.size
147 assert_equal 2, issues.first.id
147 assert_equal 2, issues.first.id
148 end
148 end
149
149
150 def test_operator_is_on_integer_custom_field
150 def test_operator_is_on_integer_custom_field
151 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
151 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
152 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
152 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
153 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
153 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
154 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
154 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
155
155
156 query = Query.new(:name => '_')
156 query = Query.new(:name => '_')
157 query.add_filter("cf_#{f.id}", '=', ['12'])
157 query.add_filter("cf_#{f.id}", '=', ['12'])
158 issues = find_issues_with_query(query)
158 issues = find_issues_with_query(query)
159 assert_equal 1, issues.size
159 assert_equal 1, issues.size
160 assert_equal 2, issues.first.id
160 assert_equal 2, issues.first.id
161 end
161 end
162
162
163 def test_operator_is_on_float_custom_field
163 def test_operator_is_on_float_custom_field
164 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
164 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
165 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
165 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
166 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
166 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
167 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
167 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
168
168
169 query = Query.new(:name => '_')
169 query = Query.new(:name => '_')
170 query.add_filter("cf_#{f.id}", '=', ['12.7'])
170 query.add_filter("cf_#{f.id}", '=', ['12.7'])
171 issues = find_issues_with_query(query)
171 issues = find_issues_with_query(query)
172 assert_equal 1, issues.size
172 assert_equal 1, issues.size
173 assert_equal 2, issues.first.id
173 assert_equal 2, issues.first.id
174 end
174 end
175
175
176 def test_operator_greater_than
176 def test_operator_greater_than
177 query = Query.new(:project => Project.find(1), :name => '_')
177 query = Query.new(:project => Project.find(1), :name => '_')
178 query.add_filter('done_ratio', '>=', ['40'])
178 query.add_filter('done_ratio', '>=', ['40'])
179 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
179 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
180 find_issues_with_query(query)
180 find_issues_with_query(query)
181 end
181 end
182
182
183 def test_operator_greater_than_a_float
183 def test_operator_greater_than_a_float
184 query = Query.new(:project => Project.find(1), :name => '_')
184 query = Query.new(:project => Project.find(1), :name => '_')
185 query.add_filter('estimated_hours', '>=', ['40.5'])
185 query.add_filter('estimated_hours', '>=', ['40.5'])
186 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
186 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
187 find_issues_with_query(query)
187 find_issues_with_query(query)
188 end
188 end
189
189
190 def test_operator_greater_than_on_int_custom_field
190 def test_operator_greater_than_on_int_custom_field
191 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
191 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
192 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
192 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
193 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
193 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
194 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
194 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
195
195
196 query = Query.new(:project => Project.find(1), :name => '_')
196 query = Query.new(:project => Project.find(1), :name => '_')
197 query.add_filter("cf_#{f.id}", '>=', ['8'])
197 query.add_filter("cf_#{f.id}", '>=', ['8'])
198 issues = find_issues_with_query(query)
198 issues = find_issues_with_query(query)
199 assert_equal 1, issues.size
199 assert_equal 1, issues.size
200 assert_equal 2, issues.first.id
200 assert_equal 2, issues.first.id
201 end
201 end
202
202
203 def test_operator_lesser_than
203 def test_operator_lesser_than
204 query = Query.new(:project => Project.find(1), :name => '_')
204 query = Query.new(:project => Project.find(1), :name => '_')
205 query.add_filter('done_ratio', '<=', ['30'])
205 query.add_filter('done_ratio', '<=', ['30'])
206 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
206 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
207 find_issues_with_query(query)
207 find_issues_with_query(query)
208 end
208 end
209
209
210 def test_operator_lesser_than_on_custom_field
210 def test_operator_lesser_than_on_custom_field
211 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
211 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
212 query = Query.new(:project => Project.find(1), :name => '_')
212 query = Query.new(:project => Project.find(1), :name => '_')
213 query.add_filter("cf_#{f.id}", '<=', ['30'])
213 query.add_filter("cf_#{f.id}", '<=', ['30'])
214 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0")
214 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0")
215 find_issues_with_query(query)
215 find_issues_with_query(query)
216 end
216 end
217
217
218 def test_operator_between
218 def test_operator_between
219 query = Query.new(:project => Project.find(1), :name => '_')
219 query = Query.new(:project => Project.find(1), :name => '_')
220 query.add_filter('done_ratio', '><', ['30', '40'])
220 query.add_filter('done_ratio', '><', ['30', '40'])
221 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
221 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
222 find_issues_with_query(query)
222 find_issues_with_query(query)
223 end
223 end
224
224
225 def test_operator_between_on_custom_field
225 def test_operator_between_on_custom_field
226 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
226 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
227 query = Query.new(:project => Project.find(1), :name => '_')
227 query = Query.new(:project => Project.find(1), :name => '_')
228 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
228 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
229 assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement
229 assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement
230 find_issues_with_query(query)
230 find_issues_with_query(query)
231 end
231 end
232
232
233 def test_date_filter_should_not_accept_non_date_values
233 def test_date_filter_should_not_accept_non_date_values
234 query = Query.new(:name => '_')
234 query = Query.new(:name => '_')
235 query.add_filter('created_on', '=', ['a'])
235 query.add_filter('created_on', '=', ['a'])
236
236
237 assert query.has_filter?('created_on')
237 assert query.has_filter?('created_on')
238 assert !query.valid?
238 assert !query.valid?
239 end
239 end
240
240
241 def test_date_filter_should_not_accept_invalid_date_values
241 def test_date_filter_should_not_accept_invalid_date_values
242 query = Query.new(:name => '_')
242 query = Query.new(:name => '_')
243 query.add_filter('created_on', '=', ['2011-01-34'])
243 query.add_filter('created_on', '=', ['2011-01-34'])
244
244
245 assert query.has_filter?('created_on')
245 assert query.has_filter?('created_on')
246 assert !query.valid?
246 assert !query.valid?
247 end
247 end
248
248
249 def test_relative_date_filter_should_not_accept_non_integer_values
249 def test_relative_date_filter_should_not_accept_non_integer_values
250 query = Query.new(:name => '_')
250 query = Query.new(:name => '_')
251 query.add_filter('created_on', '>t-', ['a'])
251 query.add_filter('created_on', '>t-', ['a'])
252
252
253 assert query.has_filter?('created_on')
253 assert query.has_filter?('created_on')
254 assert !query.valid?
254 assert !query.valid?
255 end
255 end
256
256
257 def test_operator_date_equals
257 def test_operator_date_equals
258 query = Query.new(:name => '_')
258 query = Query.new(:name => '_')
259 query.add_filter('due_date', '=', ['2011-07-10'])
259 query.add_filter('due_date', '=', ['2011-07-10'])
260 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
260 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
261 find_issues_with_query(query)
261 find_issues_with_query(query)
262 end
262 end
263
263
264 def test_operator_date_lesser_than
264 def test_operator_date_lesser_than
265 query = Query.new(:name => '_')
265 query = Query.new(:name => '_')
266 query.add_filter('due_date', '<=', ['2011-07-10'])
266 query.add_filter('due_date', '<=', ['2011-07-10'])
267 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
267 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
268 find_issues_with_query(query)
268 find_issues_with_query(query)
269 end
269 end
270
270
271 def test_operator_date_greater_than
271 def test_operator_date_greater_than
272 query = Query.new(:name => '_')
272 query = Query.new(:name => '_')
273 query.add_filter('due_date', '>=', ['2011-07-10'])
273 query.add_filter('due_date', '>=', ['2011-07-10'])
274 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
274 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
275 find_issues_with_query(query)
275 find_issues_with_query(query)
276 end
276 end
277
277
278 def test_operator_date_between
278 def test_operator_date_between
279 query = Query.new(:name => '_')
279 query = Query.new(:name => '_')
280 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
280 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
281 assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
281 assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
282 find_issues_with_query(query)
282 find_issues_with_query(query)
283 end
283 end
284
284
285 def test_operator_in_more_than
285 def test_operator_in_more_than
286 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
286 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
287 query = Query.new(:project => Project.find(1), :name => '_')
287 query = Query.new(:project => Project.find(1), :name => '_')
288 query.add_filter('due_date', '>t+', ['15'])
288 query.add_filter('due_date', '>t+', ['15'])
289 issues = find_issues_with_query(query)
289 issues = find_issues_with_query(query)
290 assert !issues.empty?
290 assert !issues.empty?
291 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
291 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
292 end
292 end
293
293
294 def test_operator_in_less_than
294 def test_operator_in_less_than
295 query = Query.new(:project => Project.find(1), :name => '_')
295 query = Query.new(:project => Project.find(1), :name => '_')
296 query.add_filter('due_date', '<t+', ['15'])
296 query.add_filter('due_date', '<t+', ['15'])
297 issues = find_issues_with_query(query)
297 issues = find_issues_with_query(query)
298 assert !issues.empty?
298 assert !issues.empty?
299 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
299 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
300 end
300 end
301
301
302 def test_operator_less_than_ago
302 def test_operator_less_than_ago
303 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
303 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
304 query = Query.new(:project => Project.find(1), :name => '_')
304 query = Query.new(:project => Project.find(1), :name => '_')
305 query.add_filter('due_date', '>t-', ['3'])
305 query.add_filter('due_date', '>t-', ['3'])
306 issues = find_issues_with_query(query)
306 issues = find_issues_with_query(query)
307 assert !issues.empty?
307 assert !issues.empty?
308 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
308 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
309 end
309 end
310
310
311 def test_operator_more_than_ago
311 def test_operator_more_than_ago
312 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
312 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
313 query = Query.new(:project => Project.find(1), :name => '_')
313 query = Query.new(:project => Project.find(1), :name => '_')
314 query.add_filter('due_date', '<t-', ['10'])
314 query.add_filter('due_date', '<t-', ['10'])
315 assert query.statement.include?("#{Issue.table_name}.due_date <=")
315 assert query.statement.include?("#{Issue.table_name}.due_date <=")
316 issues = find_issues_with_query(query)
316 issues = find_issues_with_query(query)
317 assert !issues.empty?
317 assert !issues.empty?
318 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
318 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
319 end
319 end
320
320
321 def test_operator_in
321 def test_operator_in
322 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
322 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
323 query = Query.new(:project => Project.find(1), :name => '_')
323 query = Query.new(:project => Project.find(1), :name => '_')
324 query.add_filter('due_date', 't+', ['2'])
324 query.add_filter('due_date', 't+', ['2'])
325 issues = find_issues_with_query(query)
325 issues = find_issues_with_query(query)
326 assert !issues.empty?
326 assert !issues.empty?
327 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
327 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
328 end
328 end
329
329
330 def test_operator_ago
330 def test_operator_ago
331 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
331 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
332 query = Query.new(:project => Project.find(1), :name => '_')
332 query = Query.new(:project => Project.find(1), :name => '_')
333 query.add_filter('due_date', 't-', ['3'])
333 query.add_filter('due_date', 't-', ['3'])
334 issues = find_issues_with_query(query)
334 issues = find_issues_with_query(query)
335 assert !issues.empty?
335 assert !issues.empty?
336 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
336 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
337 end
337 end
338
338
339 def test_operator_today
339 def test_operator_today
340 query = Query.new(:project => Project.find(1), :name => '_')
340 query = Query.new(:project => Project.find(1), :name => '_')
341 query.add_filter('due_date', 't', [''])
341 query.add_filter('due_date', 't', [''])
342 issues = find_issues_with_query(query)
342 issues = find_issues_with_query(query)
343 assert !issues.empty?
343 assert !issues.empty?
344 issues.each {|issue| assert_equal Date.today, issue.due_date}
344 issues.each {|issue| assert_equal Date.today, issue.due_date}
345 end
345 end
346
346
347 def test_operator_this_week_on_date
347 def test_operator_this_week_on_date
348 query = Query.new(:project => Project.find(1), :name => '_')
348 query = Query.new(:project => Project.find(1), :name => '_')
349 query.add_filter('due_date', 'w', [''])
349 query.add_filter('due_date', 'w', [''])
350 find_issues_with_query(query)
350 find_issues_with_query(query)
351 end
351 end
352
352
353 def test_operator_this_week_on_datetime
353 def test_operator_this_week_on_datetime
354 query = Query.new(:project => Project.find(1), :name => '_')
354 query = Query.new(:project => Project.find(1), :name => '_')
355 query.add_filter('created_on', 'w', [''])
355 query.add_filter('created_on', 'w', [''])
356 find_issues_with_query(query)
356 find_issues_with_query(query)
357 end
357 end
358
358
359 def test_operator_contains
359 def test_operator_contains
360 query = Query.new(:project => Project.find(1), :name => '_')
360 query = Query.new(:project => Project.find(1), :name => '_')
361 query.add_filter('subject', '~', ['uNable'])
361 query.add_filter('subject', '~', ['uNable'])
362 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
362 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
363 result = find_issues_with_query(query)
363 result = find_issues_with_query(query)
364 assert result.empty?
364 assert result.empty?
365 result.each {|issue| assert issue.subject.downcase.include?('unable') }
365 result.each {|issue| assert issue.subject.downcase.include?('unable') }
366 end
366 end
367
367
368 def test_range_for_this_week_with_week_starting_on_monday
368 def test_range_for_this_week_with_week_starting_on_monday
369 I18n.locale = :fr
369 I18n.locale = :fr
370 assert_equal '1', I18n.t(:general_first_day_of_week)
370 assert_equal '1', I18n.t(:general_first_day_of_week)
371
371
372 Date.stubs(:today).returns(Date.parse('2011-04-29'))
372 Date.stubs(:today).returns(Date.parse('2011-04-29'))
373
373
374 query = Query.new(:project => Project.find(1), :name => '_')
374 query = Query.new(:project => Project.find(1), :name => '_')
375 query.add_filter('due_date', 'w', [''])
375 query.add_filter('due_date', 'w', [''])
376 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
376 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
377 I18n.locale = :en
377 I18n.locale = :en
378 end
378 end
379
379
380 def test_range_for_this_week_with_week_starting_on_sunday
380 def test_range_for_this_week_with_week_starting_on_sunday
381 I18n.locale = :en
381 I18n.locale = :en
382 assert_equal '7', I18n.t(:general_first_day_of_week)
382 assert_equal '7', I18n.t(:general_first_day_of_week)
383
383
384 Date.stubs(:today).returns(Date.parse('2011-04-29'))
384 Date.stubs(:today).returns(Date.parse('2011-04-29'))
385
385
386 query = Query.new(:project => Project.find(1), :name => '_')
386 query = Query.new(:project => Project.find(1), :name => '_')
387 query.add_filter('due_date', 'w', [''])
387 query.add_filter('due_date', 'w', [''])
388 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
388 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
389 end
389 end
390
390
391 def test_operator_does_not_contains
391 def test_operator_does_not_contains
392 query = Query.new(:project => Project.find(1), :name => '_')
392 query = Query.new(:project => Project.find(1), :name => '_')
393 query.add_filter('subject', '!~', ['uNable'])
393 query.add_filter('subject', '!~', ['uNable'])
394 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
394 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
395 find_issues_with_query(query)
395 find_issues_with_query(query)
396 end
396 end
397
397
398 def test_filter_assigned_to_me
398 def test_filter_assigned_to_me
399 user = User.find(2)
399 user = User.find(2)
400 group = Group.find(10)
400 group = Group.find(10)
401 User.current = user
401 User.current = user
402 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
402 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
403 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
403 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
404 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
404 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
405 group.users << user
405 group.users << user
406
406
407 query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
407 query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
408 result = query.issues
408 result = query.issues
409 assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id)
409 assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id)
410
410
411 assert result.include?(i1)
411 assert result.include?(i1)
412 assert result.include?(i2)
412 assert result.include?(i2)
413 assert !result.include?(i3)
413 assert !result.include?(i3)
414 end
414 end
415
415
416 def test_filter_watched_issues
416 def test_filter_watched_issues
417 User.current = User.find(1)
417 User.current = User.find(1)
418 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
418 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
419 result = find_issues_with_query(query)
419 result = find_issues_with_query(query)
420 assert_not_nil result
420 assert_not_nil result
421 assert !result.empty?
421 assert !result.empty?
422 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
422 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
423 User.current = nil
423 User.current = nil
424 end
424 end
425
425
426 def test_filter_unwatched_issues
426 def test_filter_unwatched_issues
427 User.current = User.find(1)
427 User.current = User.find(1)
428 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
428 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
429 result = find_issues_with_query(query)
429 result = find_issues_with_query(query)
430 assert_not_nil result
430 assert_not_nil result
431 assert !result.empty?
431 assert !result.empty?
432 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
432 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
433 User.current = nil
433 User.current = nil
434 end
434 end
435
435
436 def test_statement_should_be_nil_with_no_filters
436 def test_statement_should_be_nil_with_no_filters
437 q = Query.new(:name => '_')
437 q = Query.new(:name => '_')
438 q.filters = {}
438 q.filters = {}
439
439
440 assert q.valid?
440 assert q.valid?
441 assert_nil q.statement
441 assert_nil q.statement
442 end
442 end
443
443
444 def test_default_columns
444 def test_default_columns
445 q = Query.new
445 q = Query.new
446 assert !q.columns.empty?
446 assert !q.columns.empty?
447 end
447 end
448
448
449 def test_set_column_names
449 def test_set_column_names
450 q = Query.new
450 q = Query.new
451 q.column_names = ['tracker', :subject, '', 'unknonw_column']
451 q.column_names = ['tracker', :subject, '', 'unknonw_column']
452 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
452 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
453 c = q.columns.first
453 c = q.columns.first
454 assert q.has_column?(c)
454 assert q.has_column?(c)
455 end
455 end
456
456
457 def test_query_should_preload_spent_hours
457 def test_query_should_preload_spent_hours
458 q = Query.new(:name => '_', :column_names => [:subject, :spent_hours])
458 q = Query.new(:name => '_', :column_names => [:subject, :spent_hours])
459 assert q.has_column?(:spent_hours)
459 assert q.has_column?(:spent_hours)
460 issues = q.issues
460 issues = q.issues
461 assert_not_nil issues.first.instance_variable_get("@spent_hours")
461 assert_not_nil issues.first.instance_variable_get("@spent_hours")
462 end
462 end
463
463
464 def test_groupable_columns_should_include_custom_fields
464 def test_groupable_columns_should_include_custom_fields
465 q = Query.new
465 q = Query.new
466 assert q.groupable_columns.detect {|c| c.is_a? QueryCustomFieldColumn}
466 assert q.groupable_columns.detect {|c| c.is_a? QueryCustomFieldColumn}
467 end
467 end
468
468
469 def test_grouped_with_valid_column
469 def test_grouped_with_valid_column
470 q = Query.new(:group_by => 'status')
470 q = Query.new(:group_by => 'status')
471 assert q.grouped?
471 assert q.grouped?
472 assert_not_nil q.group_by_column
472 assert_not_nil q.group_by_column
473 assert_equal :status, q.group_by_column.name
473 assert_equal :status, q.group_by_column.name
474 assert_not_nil q.group_by_statement
474 assert_not_nil q.group_by_statement
475 assert_equal 'status', q.group_by_statement
475 assert_equal 'status', q.group_by_statement
476 end
476 end
477
477
478 def test_grouped_with_invalid_column
478 def test_grouped_with_invalid_column
479 q = Query.new(:group_by => 'foo')
479 q = Query.new(:group_by => 'foo')
480 assert !q.grouped?
480 assert !q.grouped?
481 assert_nil q.group_by_column
481 assert_nil q.group_by_column
482 assert_nil q.group_by_statement
482 assert_nil q.group_by_statement
483 end
483 end
484
484
485 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
485 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
486 with_settings :user_format => 'lastname_coma_firstname' do
486 with_settings :user_format => 'lastname_coma_firstname' do
487 q = Query.new
487 q = Query.new
488 assert q.sortable_columns.has_key?('assigned_to')
488 assert q.sortable_columns.has_key?('assigned_to')
489 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
489 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
490 end
490 end
491 end
491 end
492
492
493 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
493 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
494 with_settings :user_format => 'lastname_coma_firstname' do
494 with_settings :user_format => 'lastname_coma_firstname' do
495 q = Query.new
495 q = Query.new
496 assert q.sortable_columns.has_key?('author')
496 assert q.sortable_columns.has_key?('author')
497 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
497 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
498 end
498 end
499 end
499 end
500
500
501 def test_default_sort
501 def test_default_sort
502 q = Query.new
502 q = Query.new
503 assert_equal [], q.sort_criteria
503 assert_equal [], q.sort_criteria
504 end
504 end
505
505
506 def test_set_sort_criteria_with_hash
506 def test_set_sort_criteria_with_hash
507 q = Query.new
507 q = Query.new
508 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
508 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
509 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
509 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
510 end
510 end
511
511
512 def test_set_sort_criteria_with_array
512 def test_set_sort_criteria_with_array
513 q = Query.new
513 q = Query.new
514 q.sort_criteria = [['priority', 'desc'], 'tracker']
514 q.sort_criteria = [['priority', 'desc'], 'tracker']
515 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
515 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
516 end
516 end
517
517
518 def test_create_query_with_sort
518 def test_create_query_with_sort
519 q = Query.new(:name => 'Sorted')
519 q = Query.new(:name => 'Sorted')
520 q.sort_criteria = [['priority', 'desc'], 'tracker']
520 q.sort_criteria = [['priority', 'desc'], 'tracker']
521 assert q.save
521 assert q.save
522 q.reload
522 q.reload
523 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
523 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
524 end
524 end
525
525
526 def test_sort_by_string_custom_field_asc
526 def test_sort_by_string_custom_field_asc
527 q = Query.new
527 q = Query.new
528 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
528 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
529 assert c
529 assert c
530 assert c.sortable
530 assert c.sortable
531 issues = Issue.find :all,
531 issues = Issue.find :all,
532 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
532 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
533 :conditions => q.statement,
533 :conditions => q.statement,
534 :order => "#{c.sortable} ASC"
534 :order => "#{c.sortable} ASC"
535 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
535 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
536 assert !values.empty?
536 assert !values.empty?
537 assert_equal values.sort, values
537 assert_equal values.sort, values
538 end
538 end
539
539
540 def test_sort_by_string_custom_field_desc
540 def test_sort_by_string_custom_field_desc
541 q = Query.new
541 q = Query.new
542 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
542 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
543 assert c
543 assert c
544 assert c.sortable
544 assert c.sortable
545 issues = Issue.find :all,
545 issues = Issue.find :all,
546 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
546 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
547 :conditions => q.statement,
547 :conditions => q.statement,
548 :order => "#{c.sortable} DESC"
548 :order => "#{c.sortable} DESC"
549 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
549 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
550 assert !values.empty?
550 assert !values.empty?
551 assert_equal values.sort.reverse, values
551 assert_equal values.sort.reverse, values
552 end
552 end
553
553
554 def test_sort_by_float_custom_field_asc
554 def test_sort_by_float_custom_field_asc
555 q = Query.new
555 q = Query.new
556 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
556 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
557 assert c
557 assert c
558 assert c.sortable
558 assert c.sortable
559 issues = Issue.find :all,
559 issues = Issue.find :all,
560 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
560 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
561 :conditions => q.statement,
561 :conditions => q.statement,
562 :order => "#{c.sortable} ASC"
562 :order => "#{c.sortable} ASC"
563 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
563 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
564 assert !values.empty?
564 assert !values.empty?
565 assert_equal values.sort, values
565 assert_equal values.sort, values
566 end
566 end
567
567
568 def test_invalid_query_should_raise_query_statement_invalid_error
568 def test_invalid_query_should_raise_query_statement_invalid_error
569 q = Query.new
569 q = Query.new
570 assert_raise Query::StatementInvalid do
570 assert_raise Query::StatementInvalid do
571 q.issues(:conditions => "foo = 1")
571 q.issues(:conditions => "foo = 1")
572 end
572 end
573 end
573 end
574
574
575 def test_issue_count
575 def test_issue_count
576 q = Query.new(:name => '_')
576 q = Query.new(:name => '_')
577 issue_count = q.issue_count
577 issue_count = q.issue_count
578 assert_equal q.issues.size, issue_count
578 assert_equal q.issues.size, issue_count
579 end
579 end
580
580
581 def test_issue_count_with_archived_issues
581 def test_issue_count_with_archived_issues
582 p = Project.generate!( :status => Project::STATUS_ARCHIVED )
582 p = Project.generate!( :status => Project::STATUS_ARCHIVED )
583 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
583 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
584 assert !i.visible?
584 assert !i.visible?
585
585
586 test_issue_count
586 test_issue_count
587 end
587 end
588
588
589 def test_issue_count_by_association_group
589 def test_issue_count_by_association_group
590 q = Query.new(:name => '_', :group_by => 'assigned_to')
590 q = Query.new(:name => '_', :group_by => 'assigned_to')
591 count_by_group = q.issue_count_by_group
591 count_by_group = q.issue_count_by_group
592 assert_kind_of Hash, count_by_group
592 assert_kind_of Hash, count_by_group
593 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
593 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
594 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
594 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
595 assert count_by_group.has_key?(User.find(3))
595 assert count_by_group.has_key?(User.find(3))
596 end
596 end
597
597
598 def test_issue_count_by_list_custom_field_group
598 def test_issue_count_by_list_custom_field_group
599 q = Query.new(:name => '_', :group_by => 'cf_1')
599 q = Query.new(:name => '_', :group_by => 'cf_1')
600 count_by_group = q.issue_count_by_group
600 count_by_group = q.issue_count_by_group
601 assert_kind_of Hash, count_by_group
601 assert_kind_of Hash, count_by_group
602 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
602 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
603 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
603 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
604 assert count_by_group.has_key?('MySQL')
604 assert count_by_group.has_key?('MySQL')
605 end
605 end
606
606
607 def test_issue_count_by_date_custom_field_group
607 def test_issue_count_by_date_custom_field_group
608 q = Query.new(:name => '_', :group_by => 'cf_8')
608 q = Query.new(:name => '_', :group_by => 'cf_8')
609 count_by_group = q.issue_count_by_group
609 count_by_group = q.issue_count_by_group
610 assert_kind_of Hash, count_by_group
610 assert_kind_of Hash, count_by_group
611 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
611 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
612 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
612 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
613 end
613 end
614
614
615 def test_issue_ids
615 def test_issue_ids
616 q = Query.new(:name => '_')
616 q = Query.new(:name => '_')
617 order = "issues.subject, issues.id"
617 order = "issues.subject, issues.id"
618 issues = q.issues(:order => order)
618 issues = q.issues(:order => order)
619 assert_equal issues.map(&:id).map(&:to_s), q.issue_ids(:order => order)
619 assert_equal issues.map(&:id), q.issue_ids(:order => order)
620 end
620 end
621
621
622 def test_label_for
622 def test_label_for
623 q = Query.new
623 q = Query.new
624 assert_equal 'Assignee', q.label_for('assigned_to_id')
624 assert_equal 'Assignee', q.label_for('assigned_to_id')
625 end
625 end
626
626
627 def test_editable_by
627 def test_editable_by
628 admin = User.find(1)
628 admin = User.find(1)
629 manager = User.find(2)
629 manager = User.find(2)
630 developer = User.find(3)
630 developer = User.find(3)
631
631
632 # Public query on project 1
632 # Public query on project 1
633 q = Query.find(1)
633 q = Query.find(1)
634 assert q.editable_by?(admin)
634 assert q.editable_by?(admin)
635 assert q.editable_by?(manager)
635 assert q.editable_by?(manager)
636 assert !q.editable_by?(developer)
636 assert !q.editable_by?(developer)
637
637
638 # Private query on project 1
638 # Private query on project 1
639 q = Query.find(2)
639 q = Query.find(2)
640 assert q.editable_by?(admin)
640 assert q.editable_by?(admin)
641 assert !q.editable_by?(manager)
641 assert !q.editable_by?(manager)
642 assert q.editable_by?(developer)
642 assert q.editable_by?(developer)
643
643
644 # Private query for all projects
644 # Private query for all projects
645 q = Query.find(3)
645 q = Query.find(3)
646 assert q.editable_by?(admin)
646 assert q.editable_by?(admin)
647 assert !q.editable_by?(manager)
647 assert !q.editable_by?(manager)
648 assert q.editable_by?(developer)
648 assert q.editable_by?(developer)
649
649
650 # Public query for all projects
650 # Public query for all projects
651 q = Query.find(4)
651 q = Query.find(4)
652 assert q.editable_by?(admin)
652 assert q.editable_by?(admin)
653 assert !q.editable_by?(manager)
653 assert !q.editable_by?(manager)
654 assert !q.editable_by?(developer)
654 assert !q.editable_by?(developer)
655 end
655 end
656
656
657 def test_visible_scope
657 def test_visible_scope
658 query_ids = Query.visible(User.anonymous).map(&:id)
658 query_ids = Query.visible(User.anonymous).map(&:id)
659
659
660 assert query_ids.include?(1), 'public query on public project was not visible'
660 assert query_ids.include?(1), 'public query on public project was not visible'
661 assert query_ids.include?(4), 'public query for all projects was not visible'
661 assert query_ids.include?(4), 'public query for all projects was not visible'
662 assert !query_ids.include?(2), 'private query on public project was visible'
662 assert !query_ids.include?(2), 'private query on public project was visible'
663 assert !query_ids.include?(3), 'private query for all projects was visible'
663 assert !query_ids.include?(3), 'private query for all projects was visible'
664 assert !query_ids.include?(7), 'public query on private project was visible'
664 assert !query_ids.include?(7), 'public query on private project was visible'
665 end
665 end
666
666
667 context "#available_filters" do
667 context "#available_filters" do
668 setup do
668 setup do
669 @query = Query.new(:name => "_")
669 @query = Query.new(:name => "_")
670 end
670 end
671
671
672 should "include users of visible projects in cross-project view" do
672 should "include users of visible projects in cross-project view" do
673 users = @query.available_filters["assigned_to_id"]
673 users = @query.available_filters["assigned_to_id"]
674 assert_not_nil users
674 assert_not_nil users
675 assert users[:values].map{|u|u[1]}.include?("3")
675 assert users[:values].map{|u|u[1]}.include?("3")
676 end
676 end
677
677
678 should "include visible projects in cross-project view" do
678 should "include visible projects in cross-project view" do
679 projects = @query.available_filters["project_id"]
679 projects = @query.available_filters["project_id"]
680 assert_not_nil projects
680 assert_not_nil projects
681 assert projects[:values].map{|u|u[1]}.include?("1")
681 assert projects[:values].map{|u|u[1]}.include?("1")
682 end
682 end
683
683
684 context "'member_of_group' filter" do
684 context "'member_of_group' filter" do
685 should "be present" do
685 should "be present" do
686 assert @query.available_filters.keys.include?("member_of_group")
686 assert @query.available_filters.keys.include?("member_of_group")
687 end
687 end
688
688
689 should "be an optional list" do
689 should "be an optional list" do
690 assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
690 assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
691 end
691 end
692
692
693 should "have a list of the groups as values" do
693 should "have a list of the groups as values" do
694 Group.destroy_all # No fixtures
694 Group.destroy_all # No fixtures
695 group1 = Group.generate!.reload
695 group1 = Group.generate!.reload
696 group2 = Group.generate!.reload
696 group2 = Group.generate!.reload
697
697
698 expected_group_list = [
698 expected_group_list = [
699 [group1.name, group1.id.to_s],
699 [group1.name, group1.id.to_s],
700 [group2.name, group2.id.to_s]
700 [group2.name, group2.id.to_s]
701 ]
701 ]
702 assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort
702 assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort
703 end
703 end
704
704
705 end
705 end
706
706
707 context "'assigned_to_role' filter" do
707 context "'assigned_to_role' filter" do
708 should "be present" do
708 should "be present" do
709 assert @query.available_filters.keys.include?("assigned_to_role")
709 assert @query.available_filters.keys.include?("assigned_to_role")
710 end
710 end
711
711
712 should "be an optional list" do
712 should "be an optional list" do
713 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
713 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
714 end
714 end
715
715
716 should "have a list of the Roles as values" do
716 should "have a list of the Roles as values" do
717 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
717 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
718 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
718 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
719 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
719 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
720 end
720 end
721
721
722 should "not include the built in Roles as values" do
722 should "not include the built in Roles as values" do
723 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
723 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
724 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
724 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
725 end
725 end
726
726
727 end
727 end
728
728
729 end
729 end
730
730
731 context "#statement" do
731 context "#statement" do
732 context "with 'member_of_group' filter" do
732 context "with 'member_of_group' filter" do
733 setup do
733 setup do
734 Group.destroy_all # No fixtures
734 Group.destroy_all # No fixtures
735 @user_in_group = User.generate!
735 @user_in_group = User.generate!
736 @second_user_in_group = User.generate!
736 @second_user_in_group = User.generate!
737 @user_in_group2 = User.generate!
737 @user_in_group2 = User.generate!
738 @user_not_in_group = User.generate!
738 @user_not_in_group = User.generate!
739
739
740 @group = Group.generate!.reload
740 @group = Group.generate!.reload
741 @group.users << @user_in_group
741 @group.users << @user_in_group
742 @group.users << @second_user_in_group
742 @group.users << @second_user_in_group
743
743
744 @group2 = Group.generate!.reload
744 @group2 = Group.generate!.reload
745 @group2.users << @user_in_group2
745 @group2.users << @user_in_group2
746
746
747 end
747 end
748
748
749 should "search assigned to for users in the group" do
749 should "search assigned to for users in the group" do
750 @query = Query.new(:name => '_')
750 @query = Query.new(:name => '_')
751 @query.add_filter('member_of_group', '=', [@group.id.to_s])
751 @query.add_filter('member_of_group', '=', [@group.id.to_s])
752
752
753 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
753 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
754 assert_find_issues_with_query_is_successful @query
754 assert_find_issues_with_query_is_successful @query
755 end
755 end
756
756
757 should "search not assigned to any group member (none)" do
757 should "search not assigned to any group member (none)" do
758 @query = Query.new(:name => '_')
758 @query = Query.new(:name => '_')
759 @query.add_filter('member_of_group', '!*', [''])
759 @query.add_filter('member_of_group', '!*', [''])
760
760
761 # Users not in a group
761 # Users not in a group
762 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
762 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
763 assert_find_issues_with_query_is_successful @query
763 assert_find_issues_with_query_is_successful @query
764 end
764 end
765
765
766 should "search assigned to any group member (all)" do
766 should "search assigned to any group member (all)" do
767 @query = Query.new(:name => '_')
767 @query = Query.new(:name => '_')
768 @query.add_filter('member_of_group', '*', [''])
768 @query.add_filter('member_of_group', '*', [''])
769
769
770 # Only users in a group
770 # Only users in a group
771 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
771 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
772 assert_find_issues_with_query_is_successful @query
772 assert_find_issues_with_query_is_successful @query
773 end
773 end
774
774
775 should "return an empty set with = empty group" do
775 should "return an empty set with = empty group" do
776 @empty_group = Group.generate!
776 @empty_group = Group.generate!
777 @query = Query.new(:name => '_')
777 @query = Query.new(:name => '_')
778 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
778 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
779
779
780 assert_equal [], find_issues_with_query(@query)
780 assert_equal [], find_issues_with_query(@query)
781 end
781 end
782
782
783 should "return issues with ! empty group" do
783 should "return issues with ! empty group" do
784 @empty_group = Group.generate!
784 @empty_group = Group.generate!
785 @query = Query.new(:name => '_')
785 @query = Query.new(:name => '_')
786 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
786 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
787
787
788 assert_find_issues_with_query_is_successful @query
788 assert_find_issues_with_query_is_successful @query
789 end
789 end
790 end
790 end
791
791
792 context "with 'assigned_to_role' filter" do
792 context "with 'assigned_to_role' filter" do
793 setup do
793 setup do
794 @manager_role = Role.find_by_name('Manager')
794 @manager_role = Role.find_by_name('Manager')
795 @developer_role = Role.find_by_name('Developer')
795 @developer_role = Role.find_by_name('Developer')
796
796
797 @project = Project.generate!
797 @project = Project.generate!
798 @manager = User.generate!
798 @manager = User.generate!
799 @developer = User.generate!
799 @developer = User.generate!
800 @boss = User.generate!
800 @boss = User.generate!
801 @guest = User.generate!
801 @guest = User.generate!
802 User.add_to_project(@manager, @project, @manager_role)
802 User.add_to_project(@manager, @project, @manager_role)
803 User.add_to_project(@developer, @project, @developer_role)
803 User.add_to_project(@developer, @project, @developer_role)
804 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
804 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
805
805
806 @issue1 = Issue.generate_for_project!(@project, :assigned_to_id => @manager.id)
806 @issue1 = Issue.generate_for_project!(@project, :assigned_to_id => @manager.id)
807 @issue2 = Issue.generate_for_project!(@project, :assigned_to_id => @developer.id)
807 @issue2 = Issue.generate_for_project!(@project, :assigned_to_id => @developer.id)
808 @issue3 = Issue.generate_for_project!(@project, :assigned_to_id => @boss.id)
808 @issue3 = Issue.generate_for_project!(@project, :assigned_to_id => @boss.id)
809 @issue4 = Issue.generate_for_project!(@project, :assigned_to_id => @guest.id)
809 @issue4 = Issue.generate_for_project!(@project, :assigned_to_id => @guest.id)
810 @issue5 = Issue.generate_for_project!(@project)
810 @issue5 = Issue.generate_for_project!(@project)
811 end
811 end
812
812
813 should "search assigned to for users with the Role" do
813 should "search assigned to for users with the Role" do
814 @query = Query.new(:name => '_', :project => @project)
814 @query = Query.new(:name => '_', :project => @project)
815 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
815 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
816
816
817 assert_query_result [@issue1, @issue3], @query
817 assert_query_result [@issue1, @issue3], @query
818 end
818 end
819
819
820 should "search assigned to for users with the Role on the issue project" do
820 should "search assigned to for users with the Role on the issue project" do
821 other_project = Project.generate!
821 other_project = Project.generate!
822 User.add_to_project(@developer, other_project, @manager_role)
822 User.add_to_project(@developer, other_project, @manager_role)
823
823
824 @query = Query.new(:name => '_', :project => @project)
824 @query = Query.new(:name => '_', :project => @project)
825 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
825 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
826
826
827 assert_query_result [@issue1, @issue3], @query
827 assert_query_result [@issue1, @issue3], @query
828 end
828 end
829
829
830 should "return an empty set with empty role" do
830 should "return an empty set with empty role" do
831 @empty_role = Role.generate!
831 @empty_role = Role.generate!
832 @query = Query.new(:name => '_', :project => @project)
832 @query = Query.new(:name => '_', :project => @project)
833 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
833 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
834
834
835 assert_query_result [], @query
835 assert_query_result [], @query
836 end
836 end
837
837
838 should "search assigned to for users without the Role" do
838 should "search assigned to for users without the Role" do
839 @query = Query.new(:name => '_', :project => @project)
839 @query = Query.new(:name => '_', :project => @project)
840 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
840 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
841
841
842 assert_query_result [@issue2, @issue4, @issue5], @query
842 assert_query_result [@issue2, @issue4, @issue5], @query
843 end
843 end
844
844
845 should "search assigned to for users not assigned to any Role (none)" do
845 should "search assigned to for users not assigned to any Role (none)" do
846 @query = Query.new(:name => '_', :project => @project)
846 @query = Query.new(:name => '_', :project => @project)
847 @query.add_filter('assigned_to_role', '!*', [''])
847 @query.add_filter('assigned_to_role', '!*', [''])
848
848
849 assert_query_result [@issue4, @issue5], @query
849 assert_query_result [@issue4, @issue5], @query
850 end
850 end
851
851
852 should "search assigned to for users assigned to any Role (all)" do
852 should "search assigned to for users assigned to any Role (all)" do
853 @query = Query.new(:name => '_', :project => @project)
853 @query = Query.new(:name => '_', :project => @project)
854 @query.add_filter('assigned_to_role', '*', [''])
854 @query.add_filter('assigned_to_role', '*', [''])
855
855
856 assert_query_result [@issue1, @issue2, @issue3], @query
856 assert_query_result [@issue1, @issue2, @issue3], @query
857 end
857 end
858
858
859 should "return issues with ! empty role" do
859 should "return issues with ! empty role" do
860 @empty_role = Role.generate!
860 @empty_role = Role.generate!
861 @query = Query.new(:name => '_', :project => @project)
861 @query = Query.new(:name => '_', :project => @project)
862 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
862 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
863
863
864 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
864 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
865 end
865 end
866 end
866 end
867 end
867 end
868
868
869 end
869 end
General Comments 0
You need to be logged in to leave comments. Login now