##// END OF EJS Templates
Fixed: private subprojects are listed on the issues view (#1217)....
Jean-Philippe Lang -
r1417:9e225cc63ff8
parent child
Show More
@@ -1,372 +1,369
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 QueryColumn
18 class QueryColumn
19 attr_accessor :name, :sortable, :default_order
19 attr_accessor :name, :sortable, :default_order
20 include GLoc
20 include GLoc
21
21
22 def initialize(name, options={})
22 def initialize(name, options={})
23 self.name = name
23 self.name = name
24 self.sortable = options[:sortable]
24 self.sortable = options[:sortable]
25 self.default_order = options[:default_order]
25 self.default_order = options[:default_order]
26 end
26 end
27
27
28 def caption
28 def caption
29 set_language_if_valid(User.current.language)
29 set_language_if_valid(User.current.language)
30 l("field_#{name}")
30 l("field_#{name}")
31 end
31 end
32 end
32 end
33
33
34 class QueryCustomFieldColumn < QueryColumn
34 class QueryCustomFieldColumn < QueryColumn
35
35
36 def initialize(custom_field)
36 def initialize(custom_field)
37 self.name = "cf_#{custom_field.id}".to_sym
37 self.name = "cf_#{custom_field.id}".to_sym
38 self.sortable = false
38 self.sortable = false
39 @cf = custom_field
39 @cf = custom_field
40 end
40 end
41
41
42 def caption
42 def caption
43 @cf.name
43 @cf.name
44 end
44 end
45
45
46 def custom_field
46 def custom_field
47 @cf
47 @cf
48 end
48 end
49 end
49 end
50
50
51 class Query < ActiveRecord::Base
51 class Query < ActiveRecord::Base
52 belongs_to :project
52 belongs_to :project
53 belongs_to :user
53 belongs_to :user
54 serialize :filters
54 serialize :filters
55 serialize :column_names
55 serialize :column_names
56
56
57 attr_protected :project_id, :user_id
57 attr_protected :project_id, :user_id
58
58
59 validates_presence_of :name, :on => :save
59 validates_presence_of :name, :on => :save
60 validates_length_of :name, :maximum => 255
60 validates_length_of :name, :maximum => 255
61
61
62 @@operators = { "=" => :label_equals,
62 @@operators = { "=" => :label_equals,
63 "!" => :label_not_equals,
63 "!" => :label_not_equals,
64 "o" => :label_open_issues,
64 "o" => :label_open_issues,
65 "c" => :label_closed_issues,
65 "c" => :label_closed_issues,
66 "!*" => :label_none,
66 "!*" => :label_none,
67 "*" => :label_all,
67 "*" => :label_all,
68 ">=" => '>=',
68 ">=" => '>=',
69 "<=" => '<=',
69 "<=" => '<=',
70 "<t+" => :label_in_less_than,
70 "<t+" => :label_in_less_than,
71 ">t+" => :label_in_more_than,
71 ">t+" => :label_in_more_than,
72 "t+" => :label_in,
72 "t+" => :label_in,
73 "t" => :label_today,
73 "t" => :label_today,
74 "w" => :label_this_week,
74 "w" => :label_this_week,
75 ">t-" => :label_less_than_ago,
75 ">t-" => :label_less_than_ago,
76 "<t-" => :label_more_than_ago,
76 "<t-" => :label_more_than_ago,
77 "t-" => :label_ago,
77 "t-" => :label_ago,
78 "~" => :label_contains,
78 "~" => :label_contains,
79 "!~" => :label_not_contains }
79 "!~" => :label_not_contains }
80
80
81 cattr_reader :operators
81 cattr_reader :operators
82
82
83 @@operators_by_filter_type = { :list => [ "=", "!" ],
83 @@operators_by_filter_type = { :list => [ "=", "!" ],
84 :list_status => [ "o", "=", "!", "c", "*" ],
84 :list_status => [ "o", "=", "!", "c", "*" ],
85 :list_optional => [ "=", "!", "!*", "*" ],
85 :list_optional => [ "=", "!", "!*", "*" ],
86 :list_subprojects => [ "*", "!*", "=" ],
86 :list_subprojects => [ "*", "!*", "=" ],
87 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
87 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
88 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
88 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
89 :string => [ "=", "~", "!", "!~" ],
89 :string => [ "=", "~", "!", "!~" ],
90 :text => [ "~", "!~" ],
90 :text => [ "~", "!~" ],
91 :integer => [ "=", ">=", "<=" ] }
91 :integer => [ "=", ">=", "<=" ] }
92
92
93 cattr_reader :operators_by_filter_type
93 cattr_reader :operators_by_filter_type
94
94
95 @@available_columns = [
95 @@available_columns = [
96 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
96 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
97 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
97 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
98 QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc'),
98 QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc'),
99 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
99 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
100 QueryColumn.new(:author),
100 QueryColumn.new(:author),
101 QueryColumn.new(:assigned_to, :sortable => "#{User.table_name}.lastname"),
101 QueryColumn.new(:assigned_to, :sortable => "#{User.table_name}.lastname"),
102 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
102 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
103 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
103 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
104 QueryColumn.new(:fixed_version, :sortable => "#{Version.table_name}.effective_date", :default_order => 'desc'),
104 QueryColumn.new(:fixed_version, :sortable => "#{Version.table_name}.effective_date", :default_order => 'desc'),
105 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
105 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
106 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
106 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
107 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
107 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
108 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
108 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
109 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
109 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
110 ]
110 ]
111 cattr_reader :available_columns
111 cattr_reader :available_columns
112
112
113 def initialize(attributes = nil)
113 def initialize(attributes = nil)
114 super attributes
114 super attributes
115 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
115 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
116 set_language_if_valid(User.current.language)
116 set_language_if_valid(User.current.language)
117 end
117 end
118
118
119 def after_initialize
119 def after_initialize
120 # Store the fact that project is nil (used in #editable_by?)
120 # Store the fact that project is nil (used in #editable_by?)
121 @is_for_all = project.nil?
121 @is_for_all = project.nil?
122 end
122 end
123
123
124 def validate
124 def validate
125 filters.each_key do |field|
125 filters.each_key do |field|
126 errors.add label_for(field), :activerecord_error_blank unless
126 errors.add label_for(field), :activerecord_error_blank unless
127 # filter requires one or more values
127 # filter requires one or more values
128 (values_for(field) and !values_for(field).first.blank?) or
128 (values_for(field) and !values_for(field).first.blank?) or
129 # filter doesn't require any value
129 # filter doesn't require any value
130 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
130 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
131 end if filters
131 end if filters
132 end
132 end
133
133
134 def editable_by?(user)
134 def editable_by?(user)
135 return false unless user
135 return false unless user
136 # Admin can edit them all and regular users can edit their private queries
136 # Admin can edit them all and regular users can edit their private queries
137 return true if user.admin? || (!is_public && self.user_id == user.id)
137 return true if user.admin? || (!is_public && self.user_id == user.id)
138 # Members can not edit public queries that are for all project (only admin is allowed to)
138 # Members can not edit public queries that are for all project (only admin is allowed to)
139 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
139 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
140 end
140 end
141
141
142 def available_filters
142 def available_filters
143 return @available_filters if @available_filters
143 return @available_filters if @available_filters
144
144
145 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
145 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
146
146
147 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
147 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
148 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
148 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
149 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI'], :order => 'position').collect{|s| [s.name, s.id.to_s] } },
149 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI'], :order => 'position').collect{|s| [s.name, s.id.to_s] } },
150 "subject" => { :type => :text, :order => 8 },
150 "subject" => { :type => :text, :order => 8 },
151 "created_on" => { :type => :date_past, :order => 9 },
151 "created_on" => { :type => :date_past, :order => 9 },
152 "updated_on" => { :type => :date_past, :order => 10 },
152 "updated_on" => { :type => :date_past, :order => 10 },
153 "start_date" => { :type => :date, :order => 11 },
153 "start_date" => { :type => :date, :order => 11 },
154 "due_date" => { :type => :date, :order => 12 },
154 "due_date" => { :type => :date, :order => 12 },
155 "done_ratio" => { :type => :integer, :order => 13 }}
155 "done_ratio" => { :type => :integer, :order => 13 }}
156
156
157 user_values = []
157 user_values = []
158 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
158 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
159 if project
159 if project
160 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
160 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
161 else
161 else
162 # members of the user's projects
162 # members of the user's projects
163 user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
163 user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
164 end
164 end
165 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
165 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
166 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
166 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
167
167
168 if project
168 if project
169 # project specific filters
169 # project specific filters
170 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
170 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
171 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
171 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
172 unless @project.active_children.empty?
172 unless @project.active_children.empty?
173 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
173 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
174 end
174 end
175 @project.all_custom_fields.select(&:is_filter?).each do |field|
175 @project.all_custom_fields.select(&:is_filter?).each do |field|
176 case field.field_format
176 case field.field_format
177 when "text"
177 when "text"
178 options = { :type => :text, :order => 20 }
178 options = { :type => :text, :order => 20 }
179 when "list"
179 when "list"
180 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
180 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
181 when "date"
181 when "date"
182 options = { :type => :date, :order => 20 }
182 options = { :type => :date, :order => 20 }
183 when "bool"
183 when "bool"
184 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
184 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
185 else
185 else
186 options = { :type => :string, :order => 20 }
186 options = { :type => :string, :order => 20 }
187 end
187 end
188 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
188 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
189 end
189 end
190 # remove category filter if no category defined
190 # remove category filter if no category defined
191 @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
191 @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
192 end
192 end
193 @available_filters
193 @available_filters
194 end
194 end
195
195
196 def add_filter(field, operator, values)
196 def add_filter(field, operator, values)
197 # values must be an array
197 # values must be an array
198 return unless values and values.is_a? Array # and !values.first.empty?
198 return unless values and values.is_a? Array # and !values.first.empty?
199 # check if field is defined as an available filter
199 # check if field is defined as an available filter
200 if available_filters.has_key? field
200 if available_filters.has_key? field
201 filter_options = available_filters[field]
201 filter_options = available_filters[field]
202 # check if operator is allowed for that filter
202 # check if operator is allowed for that filter
203 #if @@operators_by_filter_type[filter_options[:type]].include? operator
203 #if @@operators_by_filter_type[filter_options[:type]].include? operator
204 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
204 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
205 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
205 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
206 #end
206 #end
207 filters[field] = {:operator => operator, :values => values }
207 filters[field] = {:operator => operator, :values => values }
208 end
208 end
209 end
209 end
210
210
211 def add_short_filter(field, expression)
211 def add_short_filter(field, expression)
212 return unless expression
212 return unless expression
213 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
213 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
214 add_filter field, (parms[0] || "="), [parms[1] || ""]
214 add_filter field, (parms[0] || "="), [parms[1] || ""]
215 end
215 end
216
216
217 def has_filter?(field)
217 def has_filter?(field)
218 filters and filters[field]
218 filters and filters[field]
219 end
219 end
220
220
221 def operator_for(field)
221 def operator_for(field)
222 has_filter?(field) ? filters[field][:operator] : nil
222 has_filter?(field) ? filters[field][:operator] : nil
223 end
223 end
224
224
225 def values_for(field)
225 def values_for(field)
226 has_filter?(field) ? filters[field][:values] : nil
226 has_filter?(field) ? filters[field][:values] : nil
227 end
227 end
228
228
229 def label_for(field)
229 def label_for(field)
230 label = @available_filters[field][:name] if @available_filters.has_key?(field)
230 label = @available_filters[field][:name] if @available_filters.has_key?(field)
231 label ||= field.gsub(/\_id$/, "")
231 label ||= field.gsub(/\_id$/, "")
232 end
232 end
233
233
234 def available_columns
234 def available_columns
235 return @available_columns if @available_columns
235 return @available_columns if @available_columns
236 @available_columns = Query.available_columns
236 @available_columns = Query.available_columns
237 @available_columns += (project ?
237 @available_columns += (project ?
238 project.all_custom_fields :
238 project.all_custom_fields :
239 IssueCustomField.find(:all, :conditions => {:is_for_all => true})
239 IssueCustomField.find(:all, :conditions => {:is_for_all => true})
240 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
240 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
241 end
241 end
242
242
243 def columns
243 def columns
244 if has_default_columns?
244 if has_default_columns?
245 available_columns.select {|c| Setting.issue_list_default_columns.include?(c.name.to_s) }
245 available_columns.select {|c| Setting.issue_list_default_columns.include?(c.name.to_s) }
246 else
246 else
247 # preserve the column_names order
247 # preserve the column_names order
248 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
248 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
249 end
249 end
250 end
250 end
251
251
252 def column_names=(names)
252 def column_names=(names)
253 names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
253 names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
254 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
254 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
255 write_attribute(:column_names, names)
255 write_attribute(:column_names, names)
256 end
256 end
257
257
258 def has_column?(column)
258 def has_column?(column)
259 column_names && column_names.include?(column.name)
259 column_names && column_names.include?(column.name)
260 end
260 end
261
261
262 def has_default_columns?
262 def has_default_columns?
263 column_names.nil? || column_names.empty?
263 column_names.nil? || column_names.empty?
264 end
264 end
265
265
266 def statement
266 def statement
267 # project/subprojects clause
267 # project/subprojects clause
268 clause = ''
268 project_clauses = []
269 if project && !@project.active_children.empty?
269 if project && !@project.active_children.empty?
270 ids = [project.id]
270 ids = [project.id]
271 if has_filter?("subproject_id")
271 if has_filter?("subproject_id")
272 case operator_for("subproject_id")
272 case operator_for("subproject_id")
273 when '='
273 when '='
274 # include the selected subprojects
274 # include the selected subprojects
275 ids += values_for("subproject_id").each(&:to_i)
275 ids += values_for("subproject_id").each(&:to_i)
276 when '!*'
276 when '!*'
277 # main project only
277 # main project only
278 else
278 else
279 # all subprojects
279 # all subprojects
280 ids += project.active_children.collect{|p| p.id}
280 ids += project.child_ids
281 end
281 end
282 elsif Setting.display_subprojects_issues?
282 elsif Setting.display_subprojects_issues?
283 ids += project.active_children.collect{|p| p.id}
283 ids += project.child_ids
284 end
284 end
285 clause << "#{Issue.table_name}.project_id IN (%s)" % ids.join(',')
285 project_clauses << "#{Issue.table_name}.project_id IN (%s)" % ids.join(',')
286 elsif project
286 elsif project
287 clause << "#{Issue.table_name}.project_id = %d" % project.id
287 project_clauses << "#{Issue.table_name}.project_id = %d" % project.id
288 else
289 clause << Project.visible_by(User.current)
290 end
288 end
289 project_clauses << Project.visible_by(User.current)
291
290
292 # filters clauses
291 # filters clauses
293 filters_clauses = []
292 filters_clauses = []
294 filters.each_key do |field|
293 filters.each_key do |field|
295 next if field == "subproject_id"
294 next if field == "subproject_id"
296 v = values_for(field).clone
295 v = values_for(field).clone
297 next unless v and !v.empty?
296 next unless v and !v.empty?
298
297
299 sql = ''
298 sql = ''
300 is_custom_filter = false
299 is_custom_filter = false
301 if field =~ /^cf_(\d+)$/
300 if field =~ /^cf_(\d+)$/
302 # custom field
301 # custom field
303 db_table = CustomValue.table_name
302 db_table = CustomValue.table_name
304 db_field = 'value'
303 db_field = 'value'
305 is_custom_filter = true
304 is_custom_filter = true
306 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
305 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
307 else
306 else
308 # regular field
307 # regular field
309 db_table = Issue.table_name
308 db_table = Issue.table_name
310 db_field = field
309 db_field = field
311 sql << '('
310 sql << '('
312 end
311 end
313
312
314 # "me" value subsitution
313 # "me" value subsitution
315 if %w(assigned_to_id author_id).include?(field)
314 if %w(assigned_to_id author_id).include?(field)
316 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
315 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
317 end
316 end
318
317
319 case operator_for field
318 case operator_for field
320 when "="
319 when "="
321 sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
320 sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
322 when "!"
321 when "!"
323 sql = sql + "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
322 sql = sql + "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
324 when "!*"
323 when "!*"
325 sql = sql + "#{db_table}.#{db_field} IS NULL"
324 sql = sql + "#{db_table}.#{db_field} IS NULL"
326 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
325 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
327 when "*"
326 when "*"
328 sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
327 sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
329 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
328 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
330 when ">="
329 when ">="
331 sql = sql + "#{db_table}.#{db_field} >= #{v.first.to_i}"
330 sql = sql + "#{db_table}.#{db_field} >= #{v.first.to_i}"
332 when "<="
331 when "<="
333 sql = sql + "#{db_table}.#{db_field} <= #{v.first.to_i}"
332 sql = sql + "#{db_table}.#{db_field} <= #{v.first.to_i}"
334 when "o"
333 when "o"
335 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
334 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
336 when "c"
335 when "c"
337 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
336 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
338 when ">t-"
337 when ">t-"
339 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)]
338 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)]
340 when "<t-"
339 when "<t-"
341 sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time)
340 sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time)
342 when "t-"
341 when "t-"
343 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)]
342 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)]
344 when ">t+"
343 when ">t+"
345 sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
344 sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
346 when "<t+"
345 when "<t+"
347 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
346 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
348 when "t+"
347 when "t+"
349 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
348 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
350 when "t"
349 when "t"
351 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)]
350 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)]
352 when "w"
351 when "w"
353 from = l(:general_first_day_of_week) == '7' ?
352 from = l(:general_first_day_of_week) == '7' ?
354 # week starts on sunday
353 # week starts on sunday
355 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
354 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
356 # week starts on monday (Rails default)
355 # week starts on monday (Rails default)
357 Time.now.at_beginning_of_week
356 Time.now.at_beginning_of_week
358 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
357 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
359 when "~"
358 when "~"
360 sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
359 sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
361 when "!~"
360 when "!~"
362 sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
361 sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
363 end
362 end
364 sql << ')'
363 sql << ')'
365 filters_clauses << sql
364 filters_clauses << sql
366 end if filters and valid?
365 end if filters and valid?
367
366
368 clause << ' AND ' unless clause.empty?
367 (project_clauses + filters_clauses).join(' AND ')
369 clause << filters_clauses.join(' AND ') unless filters_clauses.empty?
370 clause
371 end
368 end
372 end
369 end
@@ -1,514 +1,545
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class IssuesController; def rescue_action(e) raise e end; end
22 class IssuesController; def rescue_action(e) raise e end; end
23
23
24 class IssuesControllerTest < Test::Unit::TestCase
24 class IssuesControllerTest < Test::Unit::TestCase
25 fixtures :projects,
25 fixtures :projects,
26 :users,
26 :users,
27 :roles,
27 :roles,
28 :members,
28 :members,
29 :issues,
29 :issues,
30 :issue_statuses,
30 :issue_statuses,
31 :trackers,
31 :trackers,
32 :projects_trackers,
32 :projects_trackers,
33 :issue_categories,
33 :issue_categories,
34 :enabled_modules,
34 :enabled_modules,
35 :enumerations,
35 :enumerations,
36 :attachments,
36 :attachments,
37 :workflows,
37 :workflows,
38 :custom_fields,
38 :custom_fields,
39 :custom_values,
39 :custom_values,
40 :custom_fields_trackers,
40 :custom_fields_trackers,
41 :time_entries
41 :time_entries
42
42
43 def setup
43 def setup
44 @controller = IssuesController.new
44 @controller = IssuesController.new
45 @request = ActionController::TestRequest.new
45 @request = ActionController::TestRequest.new
46 @response = ActionController::TestResponse.new
46 @response = ActionController::TestResponse.new
47 User.current = nil
47 User.current = nil
48 end
48 end
49
49
50 def test_index
50 def test_index
51 get :index
51 get :index
52 assert_response :success
52 assert_response :success
53 assert_template 'index.rhtml'
53 assert_template 'index.rhtml'
54 assert_not_nil assigns(:issues)
54 assert_not_nil assigns(:issues)
55 assert_nil assigns(:project)
55 assert_nil assigns(:project)
56 assert_tag :tag => 'a', :content => /Can't print recipes/
57 assert_tag :tag => 'a', :content => /Subproject issue/
58 # private projects hidden
59 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
60 assert_no_tag :tag => 'a', :content => /Issue on project 2/
56 end
61 end
57
62
58 def test_index_with_project
63 def test_index_with_project
64 Setting.display_subprojects_issues = 0
59 get :index, :project_id => 1
65 get :index, :project_id => 1
60 assert_response :success
66 assert_response :success
61 assert_template 'index.rhtml'
67 assert_template 'index.rhtml'
62 assert_not_nil assigns(:issues)
68 assert_not_nil assigns(:issues)
69 assert_tag :tag => 'a', :content => /Can't print recipes/
70 assert_no_tag :tag => 'a', :content => /Subproject issue/
71 end
72
73 def test_index_with_project_and_subprojects
74 Setting.display_subprojects_issues = 1
75 get :index, :project_id => 1
76 assert_response :success
77 assert_template 'index.rhtml'
78 assert_not_nil assigns(:issues)
79 assert_tag :tag => 'a', :content => /Can't print recipes/
80 assert_tag :tag => 'a', :content => /Subproject issue/
81 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
82 end
83
84 def test_index_with_project_and_subprojects_should_show_private_subprojects
85 @request.session[:user_id] = 2
86 Setting.display_subprojects_issues = 1
87 get :index, :project_id => 1
88 assert_response :success
89 assert_template 'index.rhtml'
90 assert_not_nil assigns(:issues)
91 assert_tag :tag => 'a', :content => /Can't print recipes/
92 assert_tag :tag => 'a', :content => /Subproject issue/
93 assert_tag :tag => 'a', :content => /Issue of a private subproject/
63 end
94 end
64
95
65 def test_index_with_project_and_filter
96 def test_index_with_project_and_filter
66 get :index, :project_id => 1, :set_filter => 1
97 get :index, :project_id => 1, :set_filter => 1
67 assert_response :success
98 assert_response :success
68 assert_template 'index.rhtml'
99 assert_template 'index.rhtml'
69 assert_not_nil assigns(:issues)
100 assert_not_nil assigns(:issues)
70 end
101 end
71
102
72 def test_index_csv_with_project
103 def test_index_csv_with_project
73 get :index, :format => 'csv'
104 get :index, :format => 'csv'
74 assert_response :success
105 assert_response :success
75 assert_not_nil assigns(:issues)
106 assert_not_nil assigns(:issues)
76 assert_equal 'text/csv', @response.content_type
107 assert_equal 'text/csv', @response.content_type
77
108
78 get :index, :project_id => 1, :format => 'csv'
109 get :index, :project_id => 1, :format => 'csv'
79 assert_response :success
110 assert_response :success
80 assert_not_nil assigns(:issues)
111 assert_not_nil assigns(:issues)
81 assert_equal 'text/csv', @response.content_type
112 assert_equal 'text/csv', @response.content_type
82 end
113 end
83
114
84 def test_index_pdf
115 def test_index_pdf
85 get :index, :format => 'pdf'
116 get :index, :format => 'pdf'
86 assert_response :success
117 assert_response :success
87 assert_not_nil assigns(:issues)
118 assert_not_nil assigns(:issues)
88 assert_equal 'application/pdf', @response.content_type
119 assert_equal 'application/pdf', @response.content_type
89
120
90 get :index, :project_id => 1, :format => 'pdf'
121 get :index, :project_id => 1, :format => 'pdf'
91 assert_response :success
122 assert_response :success
92 assert_not_nil assigns(:issues)
123 assert_not_nil assigns(:issues)
93 assert_equal 'application/pdf', @response.content_type
124 assert_equal 'application/pdf', @response.content_type
94 end
125 end
95
126
96 def test_changes
127 def test_changes
97 get :changes, :project_id => 1
128 get :changes, :project_id => 1
98 assert_response :success
129 assert_response :success
99 assert_not_nil assigns(:journals)
130 assert_not_nil assigns(:journals)
100 assert_equal 'application/atom+xml', @response.content_type
131 assert_equal 'application/atom+xml', @response.content_type
101 end
132 end
102
133
103 def test_show_by_anonymous
134 def test_show_by_anonymous
104 get :show, :id => 1
135 get :show, :id => 1
105 assert_response :success
136 assert_response :success
106 assert_template 'show.rhtml'
137 assert_template 'show.rhtml'
107 assert_not_nil assigns(:issue)
138 assert_not_nil assigns(:issue)
108 assert_equal Issue.find(1), assigns(:issue)
139 assert_equal Issue.find(1), assigns(:issue)
109
140
110 # anonymous role is allowed to add a note
141 # anonymous role is allowed to add a note
111 assert_tag :tag => 'form',
142 assert_tag :tag => 'form',
112 :descendant => { :tag => 'fieldset',
143 :descendant => { :tag => 'fieldset',
113 :child => { :tag => 'legend',
144 :child => { :tag => 'legend',
114 :content => /Notes/ } }
145 :content => /Notes/ } }
115 end
146 end
116
147
117 def test_show_by_manager
148 def test_show_by_manager
118 @request.session[:user_id] = 2
149 @request.session[:user_id] = 2
119 get :show, :id => 1
150 get :show, :id => 1
120 assert_response :success
151 assert_response :success
121
152
122 assert_tag :tag => 'form',
153 assert_tag :tag => 'form',
123 :descendant => { :tag => 'fieldset',
154 :descendant => { :tag => 'fieldset',
124 :child => { :tag => 'legend',
155 :child => { :tag => 'legend',
125 :content => /Change properties/ } },
156 :content => /Change properties/ } },
126 :descendant => { :tag => 'fieldset',
157 :descendant => { :tag => 'fieldset',
127 :child => { :tag => 'legend',
158 :child => { :tag => 'legend',
128 :content => /Log time/ } },
159 :content => /Log time/ } },
129 :descendant => { :tag => 'fieldset',
160 :descendant => { :tag => 'fieldset',
130 :child => { :tag => 'legend',
161 :child => { :tag => 'legend',
131 :content => /Notes/ } }
162 :content => /Notes/ } }
132 end
163 end
133
164
134 def test_get_new
165 def test_get_new
135 @request.session[:user_id] = 2
166 @request.session[:user_id] = 2
136 get :new, :project_id => 1, :tracker_id => 1
167 get :new, :project_id => 1, :tracker_id => 1
137 assert_response :success
168 assert_response :success
138 assert_template 'new'
169 assert_template 'new'
139
170
140 assert_tag :tag => 'input', :attributes => { :name => 'custom_fields[2]',
171 assert_tag :tag => 'input', :attributes => { :name => 'custom_fields[2]',
141 :value => 'Default string' }
172 :value => 'Default string' }
142 end
173 end
143
174
144 def test_get_new_without_tracker_id
175 def test_get_new_without_tracker_id
145 @request.session[:user_id] = 2
176 @request.session[:user_id] = 2
146 get :new, :project_id => 1
177 get :new, :project_id => 1
147 assert_response :success
178 assert_response :success
148 assert_template 'new'
179 assert_template 'new'
149
180
150 issue = assigns(:issue)
181 issue = assigns(:issue)
151 assert_not_nil issue
182 assert_not_nil issue
152 assert_equal Project.find(1).trackers.first, issue.tracker
183 assert_equal Project.find(1).trackers.first, issue.tracker
153 end
184 end
154
185
155 def test_update_new_form
186 def test_update_new_form
156 @request.session[:user_id] = 2
187 @request.session[:user_id] = 2
157 xhr :post, :new, :project_id => 1,
188 xhr :post, :new, :project_id => 1,
158 :issue => {:tracker_id => 2,
189 :issue => {:tracker_id => 2,
159 :subject => 'This is the test_new issue',
190 :subject => 'This is the test_new issue',
160 :description => 'This is the description',
191 :description => 'This is the description',
161 :priority_id => 5}
192 :priority_id => 5}
162 assert_response :success
193 assert_response :success
163 assert_template 'new'
194 assert_template 'new'
164 end
195 end
165
196
166 def test_post_new
197 def test_post_new
167 @request.session[:user_id] = 2
198 @request.session[:user_id] = 2
168 post :new, :project_id => 1,
199 post :new, :project_id => 1,
169 :issue => {:tracker_id => 1,
200 :issue => {:tracker_id => 1,
170 :subject => 'This is the test_new issue',
201 :subject => 'This is the test_new issue',
171 :description => 'This is the description',
202 :description => 'This is the description',
172 :priority_id => 5,
203 :priority_id => 5,
173 :estimated_hours => ''},
204 :estimated_hours => ''},
174 :custom_fields => {'2' => 'Value for field 2'}
205 :custom_fields => {'2' => 'Value for field 2'}
175 assert_redirected_to 'issues/show'
206 assert_redirected_to 'issues/show'
176
207
177 issue = Issue.find_by_subject('This is the test_new issue')
208 issue = Issue.find_by_subject('This is the test_new issue')
178 assert_not_nil issue
209 assert_not_nil issue
179 assert_equal 2, issue.author_id
210 assert_equal 2, issue.author_id
180 assert_nil issue.estimated_hours
211 assert_nil issue.estimated_hours
181 v = issue.custom_values.find_by_custom_field_id(2)
212 v = issue.custom_values.find_by_custom_field_id(2)
182 assert_not_nil v
213 assert_not_nil v
183 assert_equal 'Value for field 2', v.value
214 assert_equal 'Value for field 2', v.value
184 end
215 end
185
216
186 def test_post_new_without_custom_fields_param
217 def test_post_new_without_custom_fields_param
187 @request.session[:user_id] = 2
218 @request.session[:user_id] = 2
188 post :new, :project_id => 1,
219 post :new, :project_id => 1,
189 :issue => {:tracker_id => 1,
220 :issue => {:tracker_id => 1,
190 :subject => 'This is the test_new issue',
221 :subject => 'This is the test_new issue',
191 :description => 'This is the description',
222 :description => 'This is the description',
192 :priority_id => 5}
223 :priority_id => 5}
193 assert_redirected_to 'issues/show'
224 assert_redirected_to 'issues/show'
194 end
225 end
195
226
196 def test_copy_issue
227 def test_copy_issue
197 @request.session[:user_id] = 2
228 @request.session[:user_id] = 2
198 get :new, :project_id => 1, :copy_from => 1
229 get :new, :project_id => 1, :copy_from => 1
199 assert_template 'new'
230 assert_template 'new'
200 assert_not_nil assigns(:issue)
231 assert_not_nil assigns(:issue)
201 orig = Issue.find(1)
232 orig = Issue.find(1)
202 assert_equal orig.subject, assigns(:issue).subject
233 assert_equal orig.subject, assigns(:issue).subject
203 end
234 end
204
235
205 def test_get_edit
236 def test_get_edit
206 @request.session[:user_id] = 2
237 @request.session[:user_id] = 2
207 get :edit, :id => 1
238 get :edit, :id => 1
208 assert_response :success
239 assert_response :success
209 assert_template 'edit'
240 assert_template 'edit'
210 assert_not_nil assigns(:issue)
241 assert_not_nil assigns(:issue)
211 assert_equal Issue.find(1), assigns(:issue)
242 assert_equal Issue.find(1), assigns(:issue)
212 end
243 end
213
244
214 def test_get_edit_with_params
245 def test_get_edit_with_params
215 @request.session[:user_id] = 2
246 @request.session[:user_id] = 2
216 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
247 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
217 assert_response :success
248 assert_response :success
218 assert_template 'edit'
249 assert_template 'edit'
219
250
220 issue = assigns(:issue)
251 issue = assigns(:issue)
221 assert_not_nil issue
252 assert_not_nil issue
222
253
223 assert_equal 5, issue.status_id
254 assert_equal 5, issue.status_id
224 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
255 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
225 :child => { :tag => 'option',
256 :child => { :tag => 'option',
226 :content => 'Closed',
257 :content => 'Closed',
227 :attributes => { :selected => 'selected' } }
258 :attributes => { :selected => 'selected' } }
228
259
229 assert_equal 7, issue.priority_id
260 assert_equal 7, issue.priority_id
230 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
261 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
231 :child => { :tag => 'option',
262 :child => { :tag => 'option',
232 :content => 'Urgent',
263 :content => 'Urgent',
233 :attributes => { :selected => 'selected' } }
264 :attributes => { :selected => 'selected' } }
234 end
265 end
235
266
236 def test_post_edit
267 def test_post_edit
237 @request.session[:user_id] = 2
268 @request.session[:user_id] = 2
238 ActionMailer::Base.deliveries.clear
269 ActionMailer::Base.deliveries.clear
239
270
240 issue = Issue.find(1)
271 issue = Issue.find(1)
241 old_subject = issue.subject
272 old_subject = issue.subject
242 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
273 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
243
274
244 post :edit, :id => 1, :issue => {:subject => new_subject}
275 post :edit, :id => 1, :issue => {:subject => new_subject}
245 assert_redirected_to 'issues/show/1'
276 assert_redirected_to 'issues/show/1'
246 issue.reload
277 issue.reload
247 assert_equal new_subject, issue.subject
278 assert_equal new_subject, issue.subject
248
279
249 mail = ActionMailer::Base.deliveries.last
280 mail = ActionMailer::Base.deliveries.last
250 assert_kind_of TMail::Mail, mail
281 assert_kind_of TMail::Mail, mail
251 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
282 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
252 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
283 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
253 end
284 end
254
285
255 def test_post_edit_with_status_and_assignee_change
286 def test_post_edit_with_status_and_assignee_change
256 issue = Issue.find(1)
287 issue = Issue.find(1)
257 assert_equal 1, issue.status_id
288 assert_equal 1, issue.status_id
258 @request.session[:user_id] = 2
289 @request.session[:user_id] = 2
259 assert_difference('TimeEntry.count', 0) do
290 assert_difference('TimeEntry.count', 0) do
260 post :edit,
291 post :edit,
261 :id => 1,
292 :id => 1,
262 :issue => { :status_id => 2, :assigned_to_id => 3 },
293 :issue => { :status_id => 2, :assigned_to_id => 3 },
263 :notes => 'Assigned to dlopper',
294 :notes => 'Assigned to dlopper',
264 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
295 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
265 end
296 end
266 assert_redirected_to 'issues/show/1'
297 assert_redirected_to 'issues/show/1'
267 issue.reload
298 issue.reload
268 assert_equal 2, issue.status_id
299 assert_equal 2, issue.status_id
269 j = issue.journals.find(:first, :order => 'id DESC')
300 j = issue.journals.find(:first, :order => 'id DESC')
270 assert_equal 'Assigned to dlopper', j.notes
301 assert_equal 'Assigned to dlopper', j.notes
271 assert_equal 2, j.details.size
302 assert_equal 2, j.details.size
272
303
273 mail = ActionMailer::Base.deliveries.last
304 mail = ActionMailer::Base.deliveries.last
274 assert mail.body.include?("Status changed from New to Assigned")
305 assert mail.body.include?("Status changed from New to Assigned")
275 end
306 end
276
307
277 def test_post_edit_with_note_only
308 def test_post_edit_with_note_only
278 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
309 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
279 # anonymous user
310 # anonymous user
280 post :edit,
311 post :edit,
281 :id => 1,
312 :id => 1,
282 :notes => notes
313 :notes => notes
283 assert_redirected_to 'issues/show/1'
314 assert_redirected_to 'issues/show/1'
284 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
315 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
285 assert_equal notes, j.notes
316 assert_equal notes, j.notes
286 assert_equal 0, j.details.size
317 assert_equal 0, j.details.size
287 assert_equal User.anonymous, j.user
318 assert_equal User.anonymous, j.user
288
319
289 mail = ActionMailer::Base.deliveries.last
320 mail = ActionMailer::Base.deliveries.last
290 assert mail.body.include?(notes)
321 assert mail.body.include?(notes)
291 end
322 end
292
323
293 def test_post_edit_with_note_and_spent_time
324 def test_post_edit_with_note_and_spent_time
294 @request.session[:user_id] = 2
325 @request.session[:user_id] = 2
295 spent_hours_before = Issue.find(1).spent_hours
326 spent_hours_before = Issue.find(1).spent_hours
296 assert_difference('TimeEntry.count') do
327 assert_difference('TimeEntry.count') do
297 post :edit,
328 post :edit,
298 :id => 1,
329 :id => 1,
299 :notes => '2.5 hours added',
330 :notes => '2.5 hours added',
300 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
331 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
301 end
332 end
302 assert_redirected_to 'issues/show/1'
333 assert_redirected_to 'issues/show/1'
303
334
304 issue = Issue.find(1)
335 issue = Issue.find(1)
305
336
306 j = issue.journals.find(:first, :order => 'id DESC')
337 j = issue.journals.find(:first, :order => 'id DESC')
307 assert_equal '2.5 hours added', j.notes
338 assert_equal '2.5 hours added', j.notes
308 assert_equal 0, j.details.size
339 assert_equal 0, j.details.size
309
340
310 t = issue.time_entries.find(:first, :order => 'id DESC')
341 t = issue.time_entries.find(:first, :order => 'id DESC')
311 assert_not_nil t
342 assert_not_nil t
312 assert_equal 2.5, t.hours
343 assert_equal 2.5, t.hours
313 assert_equal spent_hours_before + 2.5, issue.spent_hours
344 assert_equal spent_hours_before + 2.5, issue.spent_hours
314 end
345 end
315
346
316 def test_post_edit_with_attachment_only
347 def test_post_edit_with_attachment_only
317 # anonymous user
348 # anonymous user
318 post :edit,
349 post :edit,
319 :id => 1,
350 :id => 1,
320 :notes => '',
351 :notes => '',
321 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
352 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
322 assert_redirected_to 'issues/show/1'
353 assert_redirected_to 'issues/show/1'
323 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
354 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
324 assert j.notes.blank?
355 assert j.notes.blank?
325 assert_equal 1, j.details.size
356 assert_equal 1, j.details.size
326 assert_equal 'testfile.txt', j.details.first.value
357 assert_equal 'testfile.txt', j.details.first.value
327 assert_equal User.anonymous, j.user
358 assert_equal User.anonymous, j.user
328
359
329 mail = ActionMailer::Base.deliveries.last
360 mail = ActionMailer::Base.deliveries.last
330 assert mail.body.include?('testfile.txt')
361 assert mail.body.include?('testfile.txt')
331 end
362 end
332
363
333 def test_post_edit_with_no_change
364 def test_post_edit_with_no_change
334 issue = Issue.find(1)
365 issue = Issue.find(1)
335 issue.journals.clear
366 issue.journals.clear
336 ActionMailer::Base.deliveries.clear
367 ActionMailer::Base.deliveries.clear
337
368
338 post :edit,
369 post :edit,
339 :id => 1,
370 :id => 1,
340 :notes => ''
371 :notes => ''
341 assert_redirected_to 'issues/show/1'
372 assert_redirected_to 'issues/show/1'
342
373
343 issue.reload
374 issue.reload
344 assert issue.journals.empty?
375 assert issue.journals.empty?
345 # No email should be sent
376 # No email should be sent
346 assert ActionMailer::Base.deliveries.empty?
377 assert ActionMailer::Base.deliveries.empty?
347 end
378 end
348
379
349 def test_bulk_edit
380 def test_bulk_edit
350 @request.session[:user_id] = 2
381 @request.session[:user_id] = 2
351 # update issues priority
382 # update issues priority
352 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
383 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
353 assert_response 302
384 assert_response 302
354 # check that the issues were updated
385 # check that the issues were updated
355 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
386 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
356 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
387 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
357 end
388 end
358
389
359 def test_bulk_unassign
390 def test_bulk_unassign
360 assert_not_nil Issue.find(2).assigned_to
391 assert_not_nil Issue.find(2).assigned_to
361 @request.session[:user_id] = 2
392 @request.session[:user_id] = 2
362 # unassign issues
393 # unassign issues
363 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
394 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
364 assert_response 302
395 assert_response 302
365 # check that the issues were updated
396 # check that the issues were updated
366 assert_nil Issue.find(2).assigned_to
397 assert_nil Issue.find(2).assigned_to
367 end
398 end
368
399
369 def test_move_one_issue_to_another_project
400 def test_move_one_issue_to_another_project
370 @request.session[:user_id] = 1
401 @request.session[:user_id] = 1
371 post :move, :id => 1, :new_project_id => 2
402 post :move, :id => 1, :new_project_id => 2
372 assert_redirected_to 'projects/ecookbook/issues'
403 assert_redirected_to 'projects/ecookbook/issues'
373 assert_equal 2, Issue.find(1).project_id
404 assert_equal 2, Issue.find(1).project_id
374 end
405 end
375
406
376 def test_bulk_move_to_another_project
407 def test_bulk_move_to_another_project
377 @request.session[:user_id] = 1
408 @request.session[:user_id] = 1
378 post :move, :ids => [1, 2], :new_project_id => 2
409 post :move, :ids => [1, 2], :new_project_id => 2
379 assert_redirected_to 'projects/ecookbook/issues'
410 assert_redirected_to 'projects/ecookbook/issues'
380 # Issues moved to project 2
411 # Issues moved to project 2
381 assert_equal 2, Issue.find(1).project_id
412 assert_equal 2, Issue.find(1).project_id
382 assert_equal 2, Issue.find(2).project_id
413 assert_equal 2, Issue.find(2).project_id
383 # No tracker change
414 # No tracker change
384 assert_equal 1, Issue.find(1).tracker_id
415 assert_equal 1, Issue.find(1).tracker_id
385 assert_equal 2, Issue.find(2).tracker_id
416 assert_equal 2, Issue.find(2).tracker_id
386 end
417 end
387
418
388 def test_bulk_move_to_another_tracker
419 def test_bulk_move_to_another_tracker
389 @request.session[:user_id] = 1
420 @request.session[:user_id] = 1
390 post :move, :ids => [1, 2], :new_tracker_id => 2
421 post :move, :ids => [1, 2], :new_tracker_id => 2
391 assert_redirected_to 'projects/ecookbook/issues'
422 assert_redirected_to 'projects/ecookbook/issues'
392 assert_equal 2, Issue.find(1).tracker_id
423 assert_equal 2, Issue.find(1).tracker_id
393 assert_equal 2, Issue.find(2).tracker_id
424 assert_equal 2, Issue.find(2).tracker_id
394 end
425 end
395
426
396 def test_context_menu_one_issue
427 def test_context_menu_one_issue
397 @request.session[:user_id] = 2
428 @request.session[:user_id] = 2
398 get :context_menu, :ids => [1]
429 get :context_menu, :ids => [1]
399 assert_response :success
430 assert_response :success
400 assert_template 'context_menu'
431 assert_template 'context_menu'
401 assert_tag :tag => 'a', :content => 'Edit',
432 assert_tag :tag => 'a', :content => 'Edit',
402 :attributes => { :href => '/issues/edit/1',
433 :attributes => { :href => '/issues/edit/1',
403 :class => 'icon-edit' }
434 :class => 'icon-edit' }
404 assert_tag :tag => 'a', :content => 'Closed',
435 assert_tag :tag => 'a', :content => 'Closed',
405 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
436 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
406 :class => '' }
437 :class => '' }
407 assert_tag :tag => 'a', :content => 'Immediate',
438 assert_tag :tag => 'a', :content => 'Immediate',
408 :attributes => { :href => '/issues/edit/1?issue%5Bpriority_id%5D=8',
439 :attributes => { :href => '/issues/edit/1?issue%5Bpriority_id%5D=8',
409 :class => '' }
440 :class => '' }
410 assert_tag :tag => 'a', :content => 'Dave Lopper',
441 assert_tag :tag => 'a', :content => 'Dave Lopper',
411 :attributes => { :href => '/issues/edit/1?issue%5Bassigned_to_id%5D=3',
442 :attributes => { :href => '/issues/edit/1?issue%5Bassigned_to_id%5D=3',
412 :class => '' }
443 :class => '' }
413 assert_tag :tag => 'a', :content => 'Copy',
444 assert_tag :tag => 'a', :content => 'Copy',
414 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
445 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
415 :class => 'icon-copy' }
446 :class => 'icon-copy' }
416 assert_tag :tag => 'a', :content => 'Move',
447 assert_tag :tag => 'a', :content => 'Move',
417 :attributes => { :href => '/issues/move?ids%5B%5D=1',
448 :attributes => { :href => '/issues/move?ids%5B%5D=1',
418 :class => 'icon-move' }
449 :class => 'icon-move' }
419 assert_tag :tag => 'a', :content => 'Delete',
450 assert_tag :tag => 'a', :content => 'Delete',
420 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
451 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
421 :class => 'icon-del' }
452 :class => 'icon-del' }
422 end
453 end
423
454
424 def test_context_menu_one_issue_by_anonymous
455 def test_context_menu_one_issue_by_anonymous
425 get :context_menu, :ids => [1]
456 get :context_menu, :ids => [1]
426 assert_response :success
457 assert_response :success
427 assert_template 'context_menu'
458 assert_template 'context_menu'
428 assert_tag :tag => 'a', :content => 'Delete',
459 assert_tag :tag => 'a', :content => 'Delete',
429 :attributes => { :href => '#',
460 :attributes => { :href => '#',
430 :class => 'icon-del disabled' }
461 :class => 'icon-del disabled' }
431 end
462 end
432
463
433 def test_context_menu_multiple_issues_of_same_project
464 def test_context_menu_multiple_issues_of_same_project
434 @request.session[:user_id] = 2
465 @request.session[:user_id] = 2
435 get :context_menu, :ids => [1, 2]
466 get :context_menu, :ids => [1, 2]
436 assert_response :success
467 assert_response :success
437 assert_template 'context_menu'
468 assert_template 'context_menu'
438 assert_tag :tag => 'a', :content => 'Edit',
469 assert_tag :tag => 'a', :content => 'Edit',
439 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
470 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
440 :class => 'icon-edit' }
471 :class => 'icon-edit' }
441 assert_tag :tag => 'a', :content => 'Move',
472 assert_tag :tag => 'a', :content => 'Move',
442 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
473 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
443 :class => 'icon-move' }
474 :class => 'icon-move' }
444 assert_tag :tag => 'a', :content => 'Delete',
475 assert_tag :tag => 'a', :content => 'Delete',
445 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
476 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
446 :class => 'icon-del' }
477 :class => 'icon-del' }
447 end
478 end
448
479
449 def test_context_menu_multiple_issues_of_different_project
480 def test_context_menu_multiple_issues_of_different_project
450 @request.session[:user_id] = 2
481 @request.session[:user_id] = 2
451 get :context_menu, :ids => [1, 2, 4]
482 get :context_menu, :ids => [1, 2, 4]
452 assert_response :success
483 assert_response :success
453 assert_template 'context_menu'
484 assert_template 'context_menu'
454 assert_tag :tag => 'a', :content => 'Delete',
485 assert_tag :tag => 'a', :content => 'Delete',
455 :attributes => { :href => '#',
486 :attributes => { :href => '#',
456 :class => 'icon-del disabled' }
487 :class => 'icon-del disabled' }
457 end
488 end
458
489
459 def test_destroy_issue_with_no_time_entries
490 def test_destroy_issue_with_no_time_entries
460 assert_nil TimeEntry.find_by_issue_id(2)
491 assert_nil TimeEntry.find_by_issue_id(2)
461 @request.session[:user_id] = 2
492 @request.session[:user_id] = 2
462 post :destroy, :id => 2
493 post :destroy, :id => 2
463 assert_redirected_to 'projects/ecookbook/issues'
494 assert_redirected_to 'projects/ecookbook/issues'
464 assert_nil Issue.find_by_id(2)
495 assert_nil Issue.find_by_id(2)
465 end
496 end
466
497
467 def test_destroy_issues_with_time_entries
498 def test_destroy_issues_with_time_entries
468 @request.session[:user_id] = 2
499 @request.session[:user_id] = 2
469 post :destroy, :ids => [1, 3]
500 post :destroy, :ids => [1, 3]
470 assert_response :success
501 assert_response :success
471 assert_template 'destroy'
502 assert_template 'destroy'
472 assert_not_nil assigns(:hours)
503 assert_not_nil assigns(:hours)
473 assert Issue.find_by_id(1) && Issue.find_by_id(3)
504 assert Issue.find_by_id(1) && Issue.find_by_id(3)
474 end
505 end
475
506
476 def test_destroy_issues_and_destroy_time_entries
507 def test_destroy_issues_and_destroy_time_entries
477 @request.session[:user_id] = 2
508 @request.session[:user_id] = 2
478 post :destroy, :ids => [1, 3], :todo => 'destroy'
509 post :destroy, :ids => [1, 3], :todo => 'destroy'
479 assert_redirected_to 'projects/ecookbook/issues'
510 assert_redirected_to 'projects/ecookbook/issues'
480 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
511 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
481 assert_nil TimeEntry.find_by_id([1, 2])
512 assert_nil TimeEntry.find_by_id([1, 2])
482 end
513 end
483
514
484 def test_destroy_issues_and_assign_time_entries_to_project
515 def test_destroy_issues_and_assign_time_entries_to_project
485 @request.session[:user_id] = 2
516 @request.session[:user_id] = 2
486 post :destroy, :ids => [1, 3], :todo => 'nullify'
517 post :destroy, :ids => [1, 3], :todo => 'nullify'
487 assert_redirected_to 'projects/ecookbook/issues'
518 assert_redirected_to 'projects/ecookbook/issues'
488 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
519 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
489 assert_nil TimeEntry.find(1).issue_id
520 assert_nil TimeEntry.find(1).issue_id
490 assert_nil TimeEntry.find(2).issue_id
521 assert_nil TimeEntry.find(2).issue_id
491 end
522 end
492
523
493 def test_destroy_issues_and_reassign_time_entries_to_another_issue
524 def test_destroy_issues_and_reassign_time_entries_to_another_issue
494 @request.session[:user_id] = 2
525 @request.session[:user_id] = 2
495 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
526 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
496 assert_redirected_to 'projects/ecookbook/issues'
527 assert_redirected_to 'projects/ecookbook/issues'
497 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
528 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
498 assert_equal 2, TimeEntry.find(1).issue_id
529 assert_equal 2, TimeEntry.find(1).issue_id
499 assert_equal 2, TimeEntry.find(2).issue_id
530 assert_equal 2, TimeEntry.find(2).issue_id
500 end
531 end
501
532
502 def test_destroy_attachment
533 def test_destroy_attachment
503 issue = Issue.find(3)
534 issue = Issue.find(3)
504 a = issue.attachments.size
535 a = issue.attachments.size
505 @request.session[:user_id] = 2
536 @request.session[:user_id] = 2
506 post :destroy_attachment, :id => 3, :attachment_id => 1
537 post :destroy_attachment, :id => 3, :attachment_id => 1
507 assert_redirected_to 'issues/show/3'
538 assert_redirected_to 'issues/show/3'
508 assert_nil Attachment.find_by_id(1)
539 assert_nil Attachment.find_by_id(1)
509 issue.reload
540 issue.reload
510 assert_equal((a-1), issue.attachments.size)
541 assert_equal((a-1), issue.attachments.size)
511 j = issue.journals.find(:first, :order => 'created_on DESC')
542 j = issue.journals.find(:first, :order => 'created_on DESC')
512 assert_equal 'attachment', j.details.first.property
543 assert_equal 'attachment', j.details.first.property
513 end
544 end
514 end
545 end
General Comments 0
You need to be logged in to leave comments. Login now