##// END OF EJS Templates
Ability to filter issues blocked by any/no open issues (#16621)....
Jean-Philippe Lang -
r14427:d3d7678c7689
parent child
Show More
@@ -1,555 +1,558
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssueQuery < Query
19 19
20 20 self.queried_class = Issue
21 21
22 22 self.available_columns = [
23 23 QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => '#', :frozen => true),
24 24 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
25 25 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
26 26 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
27 27 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
28 28 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
29 29 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
30 30 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
31 31 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
32 32 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
33 33 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
34 34 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
35 35 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
36 36 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
37 37 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true),
38 38 QueryColumn.new(:total_estimated_hours,
39 39 :sortable => "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" +
40 40 " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
41 41 :default_order => 'desc'),
42 42 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
43 43 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
44 44 QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'),
45 45 QueryColumn.new(:relations, :caption => :label_related_issues),
46 46 QueryColumn.new(:description, :inline => false)
47 47 ]
48 48
49 49 scope :visible, lambda {|*args|
50 50 user = args.shift || User.current
51 51 base = Project.allowed_to_condition(user, :view_issues, *args)
52 52 scope = joins("LEFT OUTER JOIN #{Project.table_name} ON #{table_name}.project_id = #{Project.table_name}.id").
53 53 where("#{table_name}.project_id IS NULL OR (#{base})")
54 54
55 55 if user.admin?
56 56 scope.where("#{table_name}.visibility <> ? OR #{table_name}.user_id = ?", VISIBILITY_PRIVATE, user.id)
57 57 elsif user.memberships.any?
58 58 scope.where("#{table_name}.visibility = ?" +
59 59 " OR (#{table_name}.visibility = ? AND #{table_name}.id IN (" +
60 60 "SELECT DISTINCT q.id FROM #{table_name} q" +
61 61 " INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" +
62 62 " INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" +
63 63 " INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" +
64 64 " WHERE q.project_id IS NULL OR q.project_id = m.project_id))" +
65 65 " OR #{table_name}.user_id = ?",
66 66 VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, user.id)
67 67 elsif user.logged?
68 68 scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id)
69 69 else
70 70 scope.where("#{table_name}.visibility = ?", VISIBILITY_PUBLIC)
71 71 end
72 72 }
73 73
74 74 def initialize(attributes=nil, *args)
75 75 super attributes
76 76 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
77 77 end
78 78
79 79 # Returns true if the query is visible to +user+ or the current user.
80 80 def visible?(user=User.current)
81 81 return true if user.admin?
82 82 return false unless project.nil? || user.allowed_to?(:view_issues, project)
83 83 case visibility
84 84 when VISIBILITY_PUBLIC
85 85 true
86 86 when VISIBILITY_ROLES
87 87 if project
88 88 (user.roles_for_project(project) & roles).any?
89 89 else
90 90 Member.where(:user_id => user.id).joins(:roles).where(:member_roles => {:role_id => roles.map(&:id)}).any?
91 91 end
92 92 else
93 93 user == self.user
94 94 end
95 95 end
96 96
97 97 def is_private?
98 98 visibility == VISIBILITY_PRIVATE
99 99 end
100 100
101 101 def is_public?
102 102 !is_private?
103 103 end
104 104
105 105 def draw_relations
106 106 r = options[:draw_relations]
107 107 r.nil? || r == '1'
108 108 end
109 109
110 110 def draw_relations=(arg)
111 111 options[:draw_relations] = (arg == '0' ? '0' : nil)
112 112 end
113 113
114 114 def draw_progress_line
115 115 r = options[:draw_progress_line]
116 116 r == '1'
117 117 end
118 118
119 119 def draw_progress_line=(arg)
120 120 options[:draw_progress_line] = (arg == '1' ? '1' : nil)
121 121 end
122 122
123 123 def build_from_params(params)
124 124 super
125 125 self.draw_relations = params[:draw_relations] || (params[:query] && params[:query][:draw_relations])
126 126 self.draw_progress_line = params[:draw_progress_line] || (params[:query] && params[:query][:draw_progress_line])
127 127 self
128 128 end
129 129
130 130 def initialize_available_filters
131 131 principals = []
132 132 subprojects = []
133 133 versions = []
134 134 categories = []
135 135 issue_custom_fields = []
136 136
137 137 if project
138 138 principals += project.principals.visible
139 139 unless project.leaf?
140 140 subprojects = project.descendants.visible.to_a
141 141 principals += Principal.member_of(subprojects).visible
142 142 end
143 143 versions = project.shared_versions.to_a
144 144 categories = project.issue_categories.to_a
145 145 issue_custom_fields = project.all_issue_custom_fields
146 146 else
147 147 if all_projects.any?
148 148 principals += Principal.member_of(all_projects).visible
149 149 end
150 150 versions = Version.visible.where(:sharing => 'system').to_a
151 151 issue_custom_fields = IssueCustomField.where(:is_for_all => true)
152 152 end
153 153 principals.uniq!
154 154 principals.sort!
155 155 principals.reject! {|p| p.is_a?(GroupBuiltin)}
156 156 users = principals.select {|p| p.is_a?(User)}
157 157
158 158 add_available_filter "status_id",
159 159 :type => :list_status, :values => IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
160 160
161 161 if project.nil?
162 162 project_values = []
163 163 if User.current.logged? && User.current.memberships.any?
164 164 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
165 165 end
166 166 project_values += all_projects_values
167 167 add_available_filter("project_id",
168 168 :type => :list, :values => project_values
169 169 ) unless project_values.empty?
170 170 end
171 171
172 172 add_available_filter "tracker_id",
173 173 :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
174 174 add_available_filter "priority_id",
175 175 :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
176 176
177 177 author_values = []
178 178 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
179 179 author_values += users.collect{|s| [s.name, s.id.to_s] }
180 180 add_available_filter("author_id",
181 181 :type => :list, :values => author_values
182 182 ) unless author_values.empty?
183 183
184 184 assigned_to_values = []
185 185 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
186 186 assigned_to_values += (Setting.issue_group_assignment? ?
187 187 principals : users).collect{|s| [s.name, s.id.to_s] }
188 188 add_available_filter("assigned_to_id",
189 189 :type => :list_optional, :values => assigned_to_values
190 190 ) unless assigned_to_values.empty?
191 191
192 192 group_values = Group.givable.visible.collect {|g| [g.name, g.id.to_s] }
193 193 add_available_filter("member_of_group",
194 194 :type => :list_optional, :values => group_values
195 195 ) unless group_values.empty?
196 196
197 197 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
198 198 add_available_filter("assigned_to_role",
199 199 :type => :list_optional, :values => role_values
200 200 ) unless role_values.empty?
201 201
202 202 if versions.any?
203 203 add_available_filter "fixed_version_id",
204 204 :type => :list_optional,
205 205 :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] }
206 206 end
207 207
208 208 if categories.any?
209 209 add_available_filter "category_id",
210 210 :type => :list_optional,
211 211 :values => categories.collect{|s| [s.name, s.id.to_s] }
212 212 end
213 213
214 214 add_available_filter "subject", :type => :text
215 215 add_available_filter "description", :type => :text
216 216 add_available_filter "created_on", :type => :date_past
217 217 add_available_filter "updated_on", :type => :date_past
218 218 add_available_filter "closed_on", :type => :date_past
219 219 add_available_filter "start_date", :type => :date
220 220 add_available_filter "due_date", :type => :date
221 221 add_available_filter "estimated_hours", :type => :float
222 222 add_available_filter "done_ratio", :type => :integer
223 223
224 224 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
225 225 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
226 226 add_available_filter "is_private",
227 227 :type => :list,
228 228 :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]]
229 229 end
230 230
231 231 if User.current.logged?
232 232 add_available_filter "watcher_id",
233 233 :type => :list, :values => [["<< #{l(:label_me)} >>", "me"]]
234 234 end
235 235
236 236 if subprojects.any?
237 237 add_available_filter "subproject_id",
238 238 :type => :list_subprojects,
239 239 :values => subprojects.collect{|s| [s.name, s.id.to_s] }
240 240 end
241 241
242 242 add_custom_fields_filters(issue_custom_fields)
243 243
244 244 add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
245 245
246 246 IssueRelation::TYPES.each do |relation_type, options|
247 247 add_available_filter relation_type, :type => :relation, :label => options[:name]
248 248 end
249 249 add_available_filter "parent_id", :type => :tree, :label => :field_parent_issue
250 250 add_available_filter "child_id", :type => :tree, :label => :label_subtask_plural
251 251
252 252 Tracker.disabled_core_fields(trackers).each {|field|
253 253 delete_available_filter field
254 254 }
255 255 end
256 256
257 257 def available_columns
258 258 return @available_columns if @available_columns
259 259 @available_columns = self.class.available_columns.dup
260 260 @available_columns += (project ?
261 261 project.all_issue_custom_fields :
262 262 IssueCustomField
263 263 ).visible.collect {|cf| QueryCustomFieldColumn.new(cf) }
264 264
265 265 if User.current.allowed_to?(:view_time_entries, project, :global => true)
266 266 index = @available_columns.find_index {|column| column.name == :total_estimated_hours}
267 267 index = (index ? index + 1 : -1)
268 268 # insert the column after total_estimated_hours or at the end
269 269 @available_columns.insert index, QueryColumn.new(:spent_hours,
270 270 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)",
271 271 :default_order => 'desc',
272 272 :caption => :label_spent_time,
273 273 :totalable => true
274 274 )
275 275 @available_columns.insert index+1, QueryColumn.new(:total_spent_hours,
276 276 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} JOIN #{Issue.table_name} subtasks ON subtasks.id = #{TimeEntry.table_name}.issue_id" +
277 277 " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
278 278 :default_order => 'desc',
279 279 :caption => :label_total_spent_time
280 280 )
281 281 end
282 282
283 283 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
284 284 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
285 285 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
286 286 end
287 287
288 288 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
289 289 @available_columns.reject! {|column|
290 290 disabled_fields.include?(column.name.to_s)
291 291 }
292 292
293 293 @available_columns
294 294 end
295 295
296 296 def default_columns_names
297 297 @default_columns_names ||= begin
298 298 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
299 299
300 300 project.present? ? default_columns : [:project] | default_columns
301 301 end
302 302 end
303 303
304 304 def base_scope
305 305 Issue.visible.joins(:status, :project).where(statement)
306 306 end
307 307
308 308 # Returns the issue count
309 309 def issue_count
310 310 base_scope.count
311 311 rescue ::ActiveRecord::StatementInvalid => e
312 312 raise StatementInvalid.new(e.message)
313 313 end
314 314
315 315 # Returns the issue count by group or nil if query is not grouped
316 316 def issue_count_by_group
317 317 grouped_query do |scope|
318 318 scope.count
319 319 end
320 320 end
321 321
322 322 # Returns sum of all the issue's estimated_hours
323 323 def total_for_estimated_hours(scope)
324 324 map_total(scope.sum(:estimated_hours)) {|t| t.to_f.round(2)}
325 325 end
326 326
327 327 # Returns sum of all the issue's time entries hours
328 328 def total_for_spent_hours(scope)
329 329 total = if group_by_column.try(:name) == :project
330 330 # TODO: remove this when https://github.com/rails/rails/issues/21922 is fixed
331 331 # We have to do a custom join without the time_entries.project_id column
332 332 # that would trigger a ambiguous column name error
333 333 scope.joins("JOIN (SELECT issue_id, hours FROM #{TimeEntry.table_name}) AS joined_time_entries ON joined_time_entries.issue_id = #{Issue.table_name}.id").
334 334 sum("joined_time_entries.hours")
335 335 else
336 336 scope.joins(:time_entries).sum("#{TimeEntry.table_name}.hours")
337 337 end
338 338 map_total(total) {|t| t.to_f.round(2)}
339 339 end
340 340
341 341 # Returns the issues
342 342 # Valid options are :order, :offset, :limit, :include, :conditions
343 343 def issues(options={})
344 344 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
345 345
346 346 scope = Issue.visible.
347 347 joins(:status, :project).
348 348 where(statement).
349 349 includes(([:status, :project] + (options[:include] || [])).uniq).
350 350 where(options[:conditions]).
351 351 order(order_option).
352 352 joins(joins_for_order_statement(order_option.join(','))).
353 353 limit(options[:limit]).
354 354 offset(options[:offset])
355 355
356 356 scope = scope.preload(:custom_values)
357 357 if has_column?(:author)
358 358 scope = scope.preload(:author)
359 359 end
360 360
361 361 issues = scope.to_a
362 362
363 363 if has_column?(:spent_hours)
364 364 Issue.load_visible_spent_hours(issues)
365 365 end
366 366 if has_column?(:total_spent_hours)
367 367 Issue.load_visible_total_spent_hours(issues)
368 368 end
369 369 if has_column?(:relations)
370 370 Issue.load_visible_relations(issues)
371 371 end
372 372 issues
373 373 rescue ::ActiveRecord::StatementInvalid => e
374 374 raise StatementInvalid.new(e.message)
375 375 end
376 376
377 377 # Returns the issues ids
378 378 def issue_ids(options={})
379 379 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
380 380
381 381 Issue.visible.
382 382 joins(:status, :project).
383 383 where(statement).
384 384 includes(([:status, :project] + (options[:include] || [])).uniq).
385 385 references(([:status, :project] + (options[:include] || [])).uniq).
386 386 where(options[:conditions]).
387 387 order(order_option).
388 388 joins(joins_for_order_statement(order_option.join(','))).
389 389 limit(options[:limit]).
390 390 offset(options[:offset]).
391 391 pluck(:id)
392 392 rescue ::ActiveRecord::StatementInvalid => e
393 393 raise StatementInvalid.new(e.message)
394 394 end
395 395
396 396 # Returns the journals
397 397 # Valid options are :order, :offset, :limit
398 398 def journals(options={})
399 399 Journal.visible.
400 400 joins(:issue => [:project, :status]).
401 401 where(statement).
402 402 order(options[:order]).
403 403 limit(options[:limit]).
404 404 offset(options[:offset]).
405 405 preload(:details, :user, {:issue => [:project, :author, :tracker, :status]}).
406 406 to_a
407 407 rescue ::ActiveRecord::StatementInvalid => e
408 408 raise StatementInvalid.new(e.message)
409 409 end
410 410
411 411 # Returns the versions
412 412 # Valid options are :conditions
413 413 def versions(options={})
414 414 Version.visible.
415 415 where(project_statement).
416 416 where(options[:conditions]).
417 417 includes(:project).
418 418 references(:project).
419 419 to_a
420 420 rescue ::ActiveRecord::StatementInvalid => e
421 421 raise StatementInvalid.new(e.message)
422 422 end
423 423
424 424 def sql_for_watcher_id_field(field, operator, value)
425 425 db_table = Watcher.table_name
426 426 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
427 427 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
428 428 end
429 429
430 430 def sql_for_member_of_group_field(field, operator, value)
431 431 if operator == '*' # Any group
432 432 groups = Group.givable
433 433 operator = '=' # Override the operator since we want to find by assigned_to
434 434 elsif operator == "!*"
435 435 groups = Group.givable
436 436 operator = '!' # Override the operator since we want to find by assigned_to
437 437 else
438 438 groups = Group.where(:id => value).to_a
439 439 end
440 440 groups ||= []
441 441
442 442 members_of_groups = groups.inject([]) {|user_ids, group|
443 443 user_ids + group.user_ids + [group.id]
444 444 }.uniq.compact.sort.collect(&:to_s)
445 445
446 446 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
447 447 end
448 448
449 449 def sql_for_assigned_to_role_field(field, operator, value)
450 450 case operator
451 451 when "*", "!*" # Member / Not member
452 452 sw = operator == "!*" ? 'NOT' : ''
453 453 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
454 454 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
455 455 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
456 456 when "=", "!"
457 457 role_cond = value.any? ?
458 458 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")" :
459 459 "1=0"
460 460
461 461 sw = operator == "!" ? 'NOT' : ''
462 462 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
463 463 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
464 464 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
465 465 end
466 466 end
467 467
468 468 def sql_for_is_private_field(field, operator, value)
469 469 op = (operator == "=" ? 'IN' : 'NOT IN')
470 470 va = value.map {|v| v == '0' ? self.class.connection.quoted_false : self.class.connection.quoted_true}.uniq.join(',')
471 471
472 472 "#{Issue.table_name}.is_private #{op} (#{va})"
473 473 end
474 474
475 475 def sql_for_parent_id_field(field, operator, value)
476 476 case operator
477 477 when "="
478 478 "#{Issue.table_name}.parent_id = #{value.first.to_i}"
479 479 when "~"
480 480 root_id, lft, rgt = Issue.where(:id => value.first.to_i).pluck(:root_id, :lft, :rgt).first
481 481 if root_id && lft && rgt
482 482 "#{Issue.table_name}.root_id = #{root_id} AND #{Issue.table_name}.lft > #{lft} AND #{Issue.table_name}.rgt < #{rgt}"
483 483 else
484 484 "1=0"
485 485 end
486 486 when "!*"
487 487 "#{Issue.table_name}.parent_id IS NULL"
488 488 when "*"
489 489 "#{Issue.table_name}.parent_id IS NOT NULL"
490 490 end
491 491 end
492 492
493 493 def sql_for_child_id_field(field, operator, value)
494 494 case operator
495 495 when "="
496 496 parent_id = Issue.where(:id => value.first.to_i).pluck(:parent_id).first
497 497 if parent_id
498 498 "#{Issue.table_name}.id = #{parent_id}"
499 499 else
500 500 "1=0"
501 501 end
502 502 when "~"
503 503 root_id, lft, rgt = Issue.where(:id => value.first.to_i).pluck(:root_id, :lft, :rgt).first
504 504 if root_id && lft && rgt
505 505 "#{Issue.table_name}.root_id = #{root_id} AND #{Issue.table_name}.lft < #{lft} AND #{Issue.table_name}.rgt > #{rgt}"
506 506 else
507 507 "1=0"
508 508 end
509 509 when "!*"
510 510 "#{Issue.table_name}.rgt - #{Issue.table_name}.lft = 1"
511 511 when "*"
512 512 "#{Issue.table_name}.rgt - #{Issue.table_name}.lft > 1"
513 513 end
514 514 end
515 515
516 516 def sql_for_relations(field, operator, value, options={})
517 517 relation_options = IssueRelation::TYPES[field]
518 518 return relation_options unless relation_options
519 519
520 520 relation_type = field
521 521 join_column, target_join_column = "issue_from_id", "issue_to_id"
522 522 if relation_options[:reverse] || options[:reverse]
523 523 relation_type = relation_options[:reverse] || relation_type
524 524 join_column, target_join_column = target_join_column, join_column
525 525 end
526 526
527 527 sql = case operator
528 528 when "*", "!*"
529 529 op = (operator == "*" ? 'IN' : 'NOT IN')
530 530 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}')"
531 531 when "=", "!"
532 532 op = (operator == "=" ? 'IN' : 'NOT IN')
533 533 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})"
534 534 when "=p", "=!p", "!p"
535 535 op = (operator == "!p" ? 'NOT IN' : 'IN')
536 536 comp = (operator == "=!p" ? '<>' : '=')
537 537 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})"
538 when "*o", "!o"
539 op = (operator == "!o" ? 'NOT IN' : 'IN')
540 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false}))"
538 541 end
539 542
540 543 if relation_options[:sym] == field && !options[:reverse]
541 544 sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
542 545 sql = sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ")
543 546 end
544 547 "(#{sql})"
545 548 end
546 549
547 550 def find_assigned_to_id_filter_values(values)
548 551 Principal.visible.where(:id => values).map {|p| [p.name, p.id.to_s]}
549 552 end
550 553 alias :find_author_id_filter_values :find_assigned_to_id_filter_values
551 554
552 555 IssueRelation::TYPES.keys.each do |relation_type|
553 556 alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
554 557 end
555 558 end
@@ -1,1036 +1,1038
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class QueryColumn
19 19 attr_accessor :name, :sortable, :groupable, :totalable, :default_order
20 20 include Redmine::I18n
21 21
22 22 def initialize(name, options={})
23 23 self.name = name
24 24 self.sortable = options[:sortable]
25 25 self.groupable = options[:groupable] || false
26 26 if groupable == true
27 27 self.groupable = name.to_s
28 28 end
29 29 self.totalable = options[:totalable] || false
30 30 self.default_order = options[:default_order]
31 31 @inline = options.key?(:inline) ? options[:inline] : true
32 32 @caption_key = options[:caption] || "field_#{name}".to_sym
33 33 @frozen = options[:frozen]
34 34 end
35 35
36 36 def caption
37 37 case @caption_key
38 38 when Symbol
39 39 l(@caption_key)
40 40 when Proc
41 41 @caption_key.call
42 42 else
43 43 @caption_key
44 44 end
45 45 end
46 46
47 47 # Returns true if the column is sortable, otherwise false
48 48 def sortable?
49 49 !@sortable.nil?
50 50 end
51 51
52 52 def sortable
53 53 @sortable.is_a?(Proc) ? @sortable.call : @sortable
54 54 end
55 55
56 56 def inline?
57 57 @inline
58 58 end
59 59
60 60 def frozen?
61 61 @frozen
62 62 end
63 63
64 64 def value(object)
65 65 object.send name
66 66 end
67 67
68 68 def value_object(object)
69 69 object.send name
70 70 end
71 71
72 72 def css_classes
73 73 name
74 74 end
75 75 end
76 76
77 77 class QueryCustomFieldColumn < QueryColumn
78 78
79 79 def initialize(custom_field)
80 80 self.name = "cf_#{custom_field.id}".to_sym
81 81 self.sortable = custom_field.order_statement || false
82 82 self.groupable = custom_field.group_statement || false
83 83 self.totalable = ['int', 'float'].include?(custom_field.field_format)
84 84 @inline = true
85 85 @cf = custom_field
86 86 end
87 87
88 88 def caption
89 89 @cf.name
90 90 end
91 91
92 92 def custom_field
93 93 @cf
94 94 end
95 95
96 96 def value_object(object)
97 97 if custom_field.visible_by?(object.project, User.current)
98 98 cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}
99 99 cv.size > 1 ? cv.sort {|a,b| a.value.to_s <=> b.value.to_s} : cv.first
100 100 else
101 101 nil
102 102 end
103 103 end
104 104
105 105 def value(object)
106 106 raw = value_object(object)
107 107 if raw.is_a?(Array)
108 108 raw.map {|r| @cf.cast_value(r.value)}
109 109 elsif raw
110 110 @cf.cast_value(raw.value)
111 111 else
112 112 nil
113 113 end
114 114 end
115 115
116 116 def css_classes
117 117 @css_classes ||= "#{name} #{@cf.field_format}"
118 118 end
119 119 end
120 120
121 121 class QueryAssociationCustomFieldColumn < QueryCustomFieldColumn
122 122
123 123 def initialize(association, custom_field)
124 124 super(custom_field)
125 125 self.name = "#{association}.cf_#{custom_field.id}".to_sym
126 126 # TODO: support sorting/grouping by association custom field
127 127 self.sortable = false
128 128 self.groupable = false
129 129 @association = association
130 130 end
131 131
132 132 def value_object(object)
133 133 if assoc = object.send(@association)
134 134 super(assoc)
135 135 end
136 136 end
137 137
138 138 def css_classes
139 139 @css_classes ||= "#{@association}_cf_#{@cf.id} #{@cf.field_format}"
140 140 end
141 141 end
142 142
143 143 class Query < ActiveRecord::Base
144 144 class StatementInvalid < ::ActiveRecord::StatementInvalid
145 145 end
146 146
147 147 VISIBILITY_PRIVATE = 0
148 148 VISIBILITY_ROLES = 1
149 149 VISIBILITY_PUBLIC = 2
150 150
151 151 belongs_to :project
152 152 belongs_to :user
153 153 has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}queries_roles#{table_name_suffix}", :foreign_key => "query_id"
154 154 serialize :filters
155 155 serialize :column_names
156 156 serialize :sort_criteria, Array
157 157 serialize :options, Hash
158 158
159 159 attr_protected :project_id, :user_id
160 160
161 161 validates_presence_of :name
162 162 validates_length_of :name, :maximum => 255
163 163 validates :visibility, :inclusion => { :in => [VISIBILITY_PUBLIC, VISIBILITY_ROLES, VISIBILITY_PRIVATE] }
164 164 validate :validate_query_filters
165 165 validate do |query|
166 166 errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) if query.visibility == VISIBILITY_ROLES && roles.blank?
167 167 end
168 168
169 169 after_save do |query|
170 170 if query.visibility_changed? && query.visibility != VISIBILITY_ROLES
171 171 query.roles.clear
172 172 end
173 173 end
174 174
175 175 class_attribute :operators
176 176 self.operators = {
177 177 "=" => :label_equals,
178 178 "!" => :label_not_equals,
179 179 "o" => :label_open_issues,
180 180 "c" => :label_closed_issues,
181 181 "!*" => :label_none,
182 182 "*" => :label_any,
183 183 ">=" => :label_greater_or_equal,
184 184 "<=" => :label_less_or_equal,
185 185 "><" => :label_between,
186 186 "<t+" => :label_in_less_than,
187 187 ">t+" => :label_in_more_than,
188 188 "><t+"=> :label_in_the_next_days,
189 189 "t+" => :label_in,
190 190 "t" => :label_today,
191 191 "ld" => :label_yesterday,
192 192 "w" => :label_this_week,
193 193 "lw" => :label_last_week,
194 194 "l2w" => [:label_last_n_weeks, {:count => 2}],
195 195 "m" => :label_this_month,
196 196 "lm" => :label_last_month,
197 197 "y" => :label_this_year,
198 198 ">t-" => :label_less_than_ago,
199 199 "<t-" => :label_more_than_ago,
200 200 "><t-"=> :label_in_the_past_days,
201 201 "t-" => :label_ago,
202 202 "~" => :label_contains,
203 203 "!~" => :label_not_contains,
204 204 "=p" => :label_any_issues_in_project,
205 205 "=!p" => :label_any_issues_not_in_project,
206 "!p" => :label_no_issues_in_project
206 "!p" => :label_no_issues_in_project,
207 "*o" => :label_any_open_issues,
208 "!o" => :label_no_open_issues
207 209 }
208 210
209 211 class_attribute :operators_by_filter_type
210 212 self.operators_by_filter_type = {
211 213 :list => [ "=", "!" ],
212 214 :list_status => [ "o", "=", "!", "c", "*" ],
213 215 :list_optional => [ "=", "!", "!*", "*" ],
214 216 :list_subprojects => [ "*", "!*", "=" ],
215 217 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
216 218 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
217 219 :string => [ "=", "~", "!", "!~", "!*", "*" ],
218 220 :text => [ "~", "!~", "!*", "*" ],
219 221 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
220 222 :float => [ "=", ">=", "<=", "><", "!*", "*" ],
221 :relation => ["=", "=p", "=!p", "!p", "!*", "*"],
223 :relation => ["=", "=p", "=!p", "!p", "*o", "!o", "!*", "*"],
222 224 :tree => ["=", "~", "!*", "*"]
223 225 }
224 226
225 227 class_attribute :available_columns
226 228 self.available_columns = []
227 229
228 230 class_attribute :queried_class
229 231
230 232 def queried_table_name
231 233 @queried_table_name ||= self.class.queried_class.table_name
232 234 end
233 235
234 236 def initialize(attributes=nil, *args)
235 237 super attributes
236 238 @is_for_all = project.nil?
237 239 end
238 240
239 241 # Builds the query from the given params
240 242 def build_from_params(params)
241 243 if params[:fields] || params[:f]
242 244 self.filters = {}
243 245 add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
244 246 else
245 247 available_filters.keys.each do |field|
246 248 add_short_filter(field, params[field]) if params[field]
247 249 end
248 250 end
249 251 self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
250 252 self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
251 253 self.totalable_names = params[:t] || (params[:query] && params[:query][:totalable_names])
252 254 self
253 255 end
254 256
255 257 # Builds a new query from the given params and attributes
256 258 def self.build_from_params(params, attributes={})
257 259 new(attributes).build_from_params(params)
258 260 end
259 261
260 262 def validate_query_filters
261 263 filters.each_key do |field|
262 264 if values_for(field)
263 265 case type_for(field)
264 266 when :integer
265 267 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
266 268 when :float
267 269 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
268 270 when :date, :date_past
269 271 case operator_for(field)
270 272 when "=", ">=", "<=", "><"
271 273 add_filter_error(field, :invalid) if values_for(field).detect {|v|
272 274 v.present? && (!v.match(/\A\d{4}-\d{2}-\d{2}(T\d{2}((:)?\d{2}){0,2}(Z|\d{2}:?\d{2})?)?\z/) || parse_date(v).nil?)
273 275 }
274 276 when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
275 277 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
276 278 end
277 279 end
278 280 end
279 281
280 282 add_filter_error(field, :blank) unless
281 283 # filter requires one or more values
282 284 (values_for(field) and !values_for(field).first.blank?) or
283 285 # filter doesn't require any value
284 ["o", "c", "!*", "*", "t", "ld", "w", "lw", "l2w", "m", "lm", "y"].include? operator_for(field)
286 ["o", "c", "!*", "*", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "*o", "!o"].include? operator_for(field)
285 287 end if filters
286 288 end
287 289
288 290 def add_filter_error(field, message)
289 291 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
290 292 errors.add(:base, m)
291 293 end
292 294
293 295 def editable_by?(user)
294 296 return false unless user
295 297 # Admin can edit them all and regular users can edit their private queries
296 298 return true if user.admin? || (is_private? && self.user_id == user.id)
297 299 # Members can not edit public queries that are for all project (only admin is allowed to)
298 300 is_public? && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
299 301 end
300 302
301 303 def trackers
302 304 @trackers ||= project.nil? ? Tracker.sorted.to_a : project.rolled_up_trackers
303 305 end
304 306
305 307 # Returns a hash of localized labels for all filter operators
306 308 def self.operators_labels
307 309 operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h}
308 310 end
309 311
310 312 # Returns a representation of the available filters for JSON serialization
311 313 def available_filters_as_json
312 314 json = {}
313 315 available_filters.each do |field, options|
314 316 options = options.slice(:type, :name, :values)
315 317 if options[:values] && values_for(field)
316 318 missing = Array(values_for(field)).select(&:present?) - options[:values].map(&:last)
317 319 if missing.any? && respond_to?(method = "find_#{field}_filter_values")
318 320 options[:values] += send(method, missing)
319 321 end
320 322 end
321 323 json[field] = options.stringify_keys
322 324 end
323 325 json
324 326 end
325 327
326 328 def all_projects
327 329 @all_projects ||= Project.visible.to_a
328 330 end
329 331
330 332 def all_projects_values
331 333 return @all_projects_values if @all_projects_values
332 334
333 335 values = []
334 336 Project.project_tree(all_projects) do |p, level|
335 337 prefix = (level > 0 ? ('--' * level + ' ') : '')
336 338 values << ["#{prefix}#{p.name}", p.id.to_s]
337 339 end
338 340 @all_projects_values = values
339 341 end
340 342
341 343 # Adds available filters
342 344 def initialize_available_filters
343 345 # implemented by sub-classes
344 346 end
345 347 protected :initialize_available_filters
346 348
347 349 # Adds an available filter
348 350 def add_available_filter(field, options)
349 351 @available_filters ||= ActiveSupport::OrderedHash.new
350 352 @available_filters[field] = options
351 353 @available_filters
352 354 end
353 355
354 356 # Removes an available filter
355 357 def delete_available_filter(field)
356 358 if @available_filters
357 359 @available_filters.delete(field)
358 360 end
359 361 end
360 362
361 363 # Return a hash of available filters
362 364 def available_filters
363 365 unless @available_filters
364 366 initialize_available_filters
365 367 @available_filters.each do |field, options|
366 368 options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, ''))
367 369 end
368 370 end
369 371 @available_filters
370 372 end
371 373
372 374 def add_filter(field, operator, values=nil)
373 375 # values must be an array
374 376 return unless values.nil? || values.is_a?(Array)
375 377 # check if field is defined as an available filter
376 378 if available_filters.has_key? field
377 379 filter_options = available_filters[field]
378 380 filters[field] = {:operator => operator, :values => (values || [''])}
379 381 end
380 382 end
381 383
382 384 def add_short_filter(field, expression)
383 385 return unless expression && available_filters.has_key?(field)
384 386 field_type = available_filters[field][:type]
385 387 operators_by_filter_type[field_type].sort.reverse.detect do |operator|
386 388 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
387 389 values = $1
388 390 add_filter field, operator, values.present? ? values.split('|') : ['']
389 391 end || add_filter(field, '=', expression.split('|'))
390 392 end
391 393
392 394 # Add multiple filters using +add_filter+
393 395 def add_filters(fields, operators, values)
394 396 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
395 397 fields.each do |field|
396 398 add_filter(field, operators[field], values && values[field])
397 399 end
398 400 end
399 401 end
400 402
401 403 def has_filter?(field)
402 404 filters and filters[field]
403 405 end
404 406
405 407 def type_for(field)
406 408 available_filters[field][:type] if available_filters.has_key?(field)
407 409 end
408 410
409 411 def operator_for(field)
410 412 has_filter?(field) ? filters[field][:operator] : nil
411 413 end
412 414
413 415 def values_for(field)
414 416 has_filter?(field) ? filters[field][:values] : nil
415 417 end
416 418
417 419 def value_for(field, index=0)
418 420 (values_for(field) || [])[index]
419 421 end
420 422
421 423 def label_for(field)
422 424 label = available_filters[field][:name] if available_filters.has_key?(field)
423 425 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
424 426 end
425 427
426 428 def self.add_available_column(column)
427 429 self.available_columns << (column) if column.is_a?(QueryColumn)
428 430 end
429 431
430 432 # Returns an array of columns that can be used to group the results
431 433 def groupable_columns
432 434 available_columns.select {|c| c.groupable}
433 435 end
434 436
435 437 # Returns a Hash of columns and the key for sorting
436 438 def sortable_columns
437 439 available_columns.inject({}) {|h, column|
438 440 h[column.name.to_s] = column.sortable
439 441 h
440 442 }
441 443 end
442 444
443 445 def columns
444 446 # preserve the column_names order
445 447 cols = (has_default_columns? ? default_columns_names : column_names).collect do |name|
446 448 available_columns.find { |col| col.name == name }
447 449 end.compact
448 450 available_columns.select(&:frozen?) | cols
449 451 end
450 452
451 453 def inline_columns
452 454 columns.select(&:inline?)
453 455 end
454 456
455 457 def block_columns
456 458 columns.reject(&:inline?)
457 459 end
458 460
459 461 def available_inline_columns
460 462 available_columns.select(&:inline?)
461 463 end
462 464
463 465 def available_block_columns
464 466 available_columns.reject(&:inline?)
465 467 end
466 468
467 469 def available_totalable_columns
468 470 available_columns.select(&:totalable)
469 471 end
470 472
471 473 def default_columns_names
472 474 []
473 475 end
474 476
475 477 def column_names=(names)
476 478 if names
477 479 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
478 480 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
479 481 # Set column_names to nil if default columns
480 482 if names == default_columns_names
481 483 names = nil
482 484 end
483 485 end
484 486 write_attribute(:column_names, names)
485 487 end
486 488
487 489 def has_column?(column)
488 490 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
489 491 end
490 492
491 493 def has_custom_field_column?
492 494 columns.any? {|column| column.is_a? QueryCustomFieldColumn}
493 495 end
494 496
495 497 def has_default_columns?
496 498 column_names.nil? || column_names.empty?
497 499 end
498 500
499 501 def totalable_columns
500 502 names = totalable_names
501 503 available_totalable_columns.select {|column| names.include?(column.name)}
502 504 end
503 505
504 506 def totalable_names=(names)
505 507 if names
506 508 names = names.select(&:present?).map {|n| n.is_a?(Symbol) ? n : n.to_sym}
507 509 end
508 510 options[:totalable_names] = names
509 511 end
510 512
511 513 def totalable_names
512 514 options[:totalable_names] || Setting.issue_list_default_totals.map(&:to_sym) || []
513 515 end
514 516
515 517 def sort_criteria=(arg)
516 518 c = []
517 519 if arg.is_a?(Hash)
518 520 arg = arg.keys.sort.collect {|k| arg[k]}
519 521 end
520 522 if arg
521 523 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']}
522 524 end
523 525 write_attribute(:sort_criteria, c)
524 526 end
525 527
526 528 def sort_criteria
527 529 read_attribute(:sort_criteria) || []
528 530 end
529 531
530 532 def sort_criteria_key(arg)
531 533 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
532 534 end
533 535
534 536 def sort_criteria_order(arg)
535 537 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
536 538 end
537 539
538 540 def sort_criteria_order_for(key)
539 541 sort_criteria.detect {|k, order| key.to_s == k}.try(:last)
540 542 end
541 543
542 544 # Returns the SQL sort order that should be prepended for grouping
543 545 def group_by_sort_order
544 546 if grouped? && (column = group_by_column)
545 547 order = (sort_criteria_order_for(column.name) || column.default_order).try(:upcase)
546 548 column.sortable.is_a?(Array) ?
547 549 column.sortable.collect {|s| "#{s} #{order}"}.join(',') :
548 550 "#{column.sortable} #{order}"
549 551 end
550 552 end
551 553
552 554 # Returns true if the query is a grouped query
553 555 def grouped?
554 556 !group_by_column.nil?
555 557 end
556 558
557 559 def group_by_column
558 560 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
559 561 end
560 562
561 563 def group_by_statement
562 564 group_by_column.try(:groupable)
563 565 end
564 566
565 567 def project_statement
566 568 project_clauses = []
567 569 if project && !project.descendants.active.empty?
568 570 ids = [project.id]
569 571 if has_filter?("subproject_id")
570 572 case operator_for("subproject_id")
571 573 when '='
572 574 # include the selected subprojects
573 575 ids += values_for("subproject_id").each(&:to_i)
574 576 when '!*'
575 577 # main project only
576 578 else
577 579 # all subprojects
578 580 ids += project.descendants.collect(&:id)
579 581 end
580 582 elsif Setting.display_subprojects_issues?
581 583 ids += project.descendants.collect(&:id)
582 584 end
583 585 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
584 586 elsif project
585 587 project_clauses << "#{Project.table_name}.id = %d" % project.id
586 588 end
587 589 project_clauses.any? ? project_clauses.join(' AND ') : nil
588 590 end
589 591
590 592 def statement
591 593 # filters clauses
592 594 filters_clauses = []
593 595 filters.each_key do |field|
594 596 next if field == "subproject_id"
595 597 v = values_for(field).clone
596 598 next unless v and !v.empty?
597 599 operator = operator_for(field)
598 600
599 601 # "me" value substitution
600 602 if %w(assigned_to_id author_id user_id watcher_id).include?(field)
601 603 if v.delete("me")
602 604 if User.current.logged?
603 605 v.push(User.current.id.to_s)
604 606 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
605 607 else
606 608 v.push("0")
607 609 end
608 610 end
609 611 end
610 612
611 613 if field == 'project_id'
612 614 if v.delete('mine')
613 615 v += User.current.memberships.map(&:project_id).map(&:to_s)
614 616 end
615 617 end
616 618
617 619 if field =~ /cf_(\d+)$/
618 620 # custom field
619 621 filters_clauses << sql_for_custom_field(field, operator, v, $1)
620 622 elsif respond_to?("sql_for_#{field}_field")
621 623 # specific statement
622 624 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
623 625 else
624 626 # regular field
625 627 filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')'
626 628 end
627 629 end if filters and valid?
628 630
629 631 if (c = group_by_column) && c.is_a?(QueryCustomFieldColumn)
630 632 # Excludes results for which the grouped custom field is not visible
631 633 filters_clauses << c.custom_field.visibility_by_project_condition
632 634 end
633 635
634 636 filters_clauses << project_statement
635 637 filters_clauses.reject!(&:blank?)
636 638
637 639 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
638 640 end
639 641
640 642 # Returns the sum of values for the given column
641 643 def total_for(column)
642 644 total_with_scope(column, base_scope)
643 645 end
644 646
645 647 # Returns a hash of the sum of the given column for each group,
646 648 # or nil if the query is not grouped
647 649 def total_by_group_for(column)
648 650 grouped_query do |scope|
649 651 total_with_scope(column, scope)
650 652 end
651 653 end
652 654
653 655 def totals
654 656 totals = totalable_columns.map {|column| [column, total_for(column)]}
655 657 yield totals if block_given?
656 658 totals
657 659 end
658 660
659 661 def totals_by_group
660 662 totals = totalable_columns.map {|column| [column, total_by_group_for(column)]}
661 663 yield totals if block_given?
662 664 totals
663 665 end
664 666
665 667 private
666 668
667 669 def grouped_query(&block)
668 670 r = nil
669 671 if grouped?
670 672 begin
671 673 # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value
672 674 r = yield base_group_scope
673 675 rescue ActiveRecord::RecordNotFound
674 676 r = {nil => yield(base_scope)}
675 677 end
676 678 c = group_by_column
677 679 if c.is_a?(QueryCustomFieldColumn)
678 680 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
679 681 end
680 682 end
681 683 r
682 684 rescue ::ActiveRecord::StatementInvalid => e
683 685 raise StatementInvalid.new(e.message)
684 686 end
685 687
686 688 def total_with_scope(column, scope)
687 689 unless column.is_a?(QueryColumn)
688 690 column = column.to_sym
689 691 column = available_totalable_columns.detect {|c| c.name == column}
690 692 end
691 693 if column.is_a?(QueryCustomFieldColumn)
692 694 custom_field = column.custom_field
693 695 send "total_for_#{custom_field.field_format}_custom_field", custom_field, scope
694 696 else
695 697 send "total_for_#{column.name}", scope
696 698 end
697 699 rescue ::ActiveRecord::StatementInvalid => e
698 700 raise StatementInvalid.new(e.message)
699 701 end
700 702
701 703 def base_scope
702 704 raise "unimplemented"
703 705 end
704 706
705 707 def base_group_scope
706 708 base_scope.
707 709 joins(joins_for_order_statement(group_by_statement)).
708 710 group(group_by_statement)
709 711 end
710 712
711 713 def total_for_float_custom_field(custom_field, scope)
712 714 total_for_custom_field(custom_field, scope) {|t| t.to_f.round(2)}
713 715 end
714 716
715 717 def total_for_int_custom_field(custom_field, scope)
716 718 total_for_custom_field(custom_field, scope) {|t| t.to_i}
717 719 end
718 720
719 721 def total_for_custom_field(custom_field, scope, &block)
720 722 total = scope.joins(:custom_values).
721 723 where(:custom_values => {:custom_field_id => custom_field.id}).
722 724 where.not(:custom_values => {:value => ''}).
723 725 sum("CAST(#{CustomValue.table_name}.value AS decimal(30,3))")
724 726
725 727 total = map_total(total, &block) if block_given?
726 728 total
727 729 end
728 730
729 731 def map_total(total, &block)
730 732 if total.is_a?(Hash)
731 733 total.keys.each {|k| total[k] = yield total[k]}
732 734 else
733 735 total = yield total
734 736 end
735 737 total
736 738 end
737 739
738 740 def sql_for_custom_field(field, operator, value, custom_field_id)
739 741 db_table = CustomValue.table_name
740 742 db_field = 'value'
741 743 filter = @available_filters[field]
742 744 return nil unless filter
743 745 if filter[:field].format.target_class && filter[:field].format.target_class <= User
744 746 if value.delete('me')
745 747 value.push User.current.id.to_s
746 748 end
747 749 end
748 750 not_in = nil
749 751 if operator == '!'
750 752 # Makes ! operator work for custom fields with multiple values
751 753 operator = '='
752 754 not_in = 'NOT'
753 755 end
754 756 customized_key = "id"
755 757 customized_class = queried_class
756 758 if field =~ /^(.+)\.cf_/
757 759 assoc = $1
758 760 customized_key = "#{assoc}_id"
759 761 customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil
760 762 raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class
761 763 end
762 764 where = sql_for_field(field, operator, value, db_table, db_field, true)
763 765 if operator =~ /[<>]/
764 766 where = "(#{where}) AND #{db_table}.#{db_field} <> ''"
765 767 end
766 768 "#{queried_table_name}.#{customized_key} #{not_in} IN (" +
767 769 "SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name}" +
768 770 " LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='#{customized_class}' AND #{db_table}.customized_id=#{customized_class.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id}" +
769 771 " WHERE (#{where}) AND (#{filter[:field].visibility_by_project_condition}))"
770 772 end
771 773
772 774 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
773 775 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
774 776 sql = ''
775 777 case operator
776 778 when "="
777 779 if value.any?
778 780 case type_for(field)
779 781 when :date, :date_past
780 782 sql = date_clause(db_table, db_field, parse_date(value.first), parse_date(value.first), is_custom_filter)
781 783 when :integer
782 784 if is_custom_filter
783 785 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) = #{value.first.to_i})"
784 786 else
785 787 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
786 788 end
787 789 when :float
788 790 if is_custom_filter
789 791 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})"
790 792 else
791 793 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
792 794 end
793 795 else
794 796 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")"
795 797 end
796 798 else
797 799 # IN an empty set
798 800 sql = "1=0"
799 801 end
800 802 when "!"
801 803 if value.any?
802 804 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + "))"
803 805 else
804 806 # NOT IN an empty set
805 807 sql = "1=1"
806 808 end
807 809 when "!*"
808 810 sql = "#{db_table}.#{db_field} IS NULL"
809 811 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
810 812 when "*"
811 813 sql = "#{db_table}.#{db_field} IS NOT NULL"
812 814 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
813 815 when ">="
814 816 if [:date, :date_past].include?(type_for(field))
815 817 sql = date_clause(db_table, db_field, parse_date(value.first), nil, is_custom_filter)
816 818 else
817 819 if is_custom_filter
818 820 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) >= #{value.first.to_f})"
819 821 else
820 822 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
821 823 end
822 824 end
823 825 when "<="
824 826 if [:date, :date_past].include?(type_for(field))
825 827 sql = date_clause(db_table, db_field, nil, parse_date(value.first), is_custom_filter)
826 828 else
827 829 if is_custom_filter
828 830 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) <= #{value.first.to_f})"
829 831 else
830 832 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
831 833 end
832 834 end
833 835 when "><"
834 836 if [:date, :date_past].include?(type_for(field))
835 837 sql = date_clause(db_table, db_field, parse_date(value[0]), parse_date(value[1]), is_custom_filter)
836 838 else
837 839 if is_custom_filter
838 840 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})"
839 841 else
840 842 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
841 843 end
842 844 end
843 845 when "o"
844 846 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false})" if field == "status_id"
845 847 when "c"
846 848 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_true})" if field == "status_id"
847 849 when "><t-"
848 850 # between today - n days and today
849 851 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0, is_custom_filter)
850 852 when ">t-"
851 853 # >= today - n days
852 854 sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil, is_custom_filter)
853 855 when "<t-"
854 856 # <= today - n days
855 857 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i, is_custom_filter)
856 858 when "t-"
857 859 # = n days in past
858 860 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i, is_custom_filter)
859 861 when "><t+"
860 862 # between today and today + n days
861 863 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i, is_custom_filter)
862 864 when ">t+"
863 865 # >= today + n days
864 866 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil, is_custom_filter)
865 867 when "<t+"
866 868 # <= today + n days
867 869 sql = relative_date_clause(db_table, db_field, nil, value.first.to_i, is_custom_filter)
868 870 when "t+"
869 871 # = today + n days
870 872 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i, is_custom_filter)
871 873 when "t"
872 874 # = today
873 875 sql = relative_date_clause(db_table, db_field, 0, 0, is_custom_filter)
874 876 when "ld"
875 877 # = yesterday
876 878 sql = relative_date_clause(db_table, db_field, -1, -1, is_custom_filter)
877 879 when "w"
878 880 # = this week
879 881 first_day_of_week = l(:general_first_day_of_week).to_i
880 882 day_of_week = Date.today.cwday
881 883 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
882 884 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6, is_custom_filter)
883 885 when "lw"
884 886 # = last week
885 887 first_day_of_week = l(:general_first_day_of_week).to_i
886 888 day_of_week = Date.today.cwday
887 889 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
888 890 sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1, is_custom_filter)
889 891 when "l2w"
890 892 # = last 2 weeks
891 893 first_day_of_week = l(:general_first_day_of_week).to_i
892 894 day_of_week = Date.today.cwday
893 895 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
894 896 sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1, is_custom_filter)
895 897 when "m"
896 898 # = this month
897 899 date = Date.today
898 900 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month, is_custom_filter)
899 901 when "lm"
900 902 # = last month
901 903 date = Date.today.prev_month
902 904 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month, is_custom_filter)
903 905 when "y"
904 906 # = this year
905 907 date = Date.today
906 908 sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year, is_custom_filter)
907 909 when "~"
908 910 sql = sql_contains("#{db_table}.#{db_field}", value.first)
909 911 when "!~"
910 912 sql = sql_contains("#{db_table}.#{db_field}", value.first, false)
911 913 else
912 914 raise "Unknown query operator #{operator}"
913 915 end
914 916
915 917 return sql
916 918 end
917 919
918 920 # Returns a SQL LIKE statement with wildcards
919 921 def sql_contains(db_field, value, match=true)
920 922 value = "'%#{self.class.connection.quote_string(value.to_s)}%'"
921 923 Redmine::Database.like(db_field, value, :match => match)
922 924 end
923 925
924 926 # Adds a filter for the given custom field
925 927 def add_custom_field_filter(field, assoc=nil)
926 928 options = field.query_filter_options(self)
927 929 if field.format.target_class && field.format.target_class <= User
928 930 if options[:values].is_a?(Array) && User.current.logged?
929 931 options[:values].unshift ["<< #{l(:label_me)} >>", "me"]
930 932 end
931 933 end
932 934
933 935 filter_id = "cf_#{field.id}"
934 936 filter_name = field.name
935 937 if assoc.present?
936 938 filter_id = "#{assoc}.#{filter_id}"
937 939 filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
938 940 end
939 941 add_available_filter filter_id, options.merge({
940 942 :name => filter_name,
941 943 :field => field
942 944 })
943 945 end
944 946
945 947 # Adds filters for the given custom fields scope
946 948 def add_custom_fields_filters(scope, assoc=nil)
947 949 scope.visible.where(:is_filter => true).sorted.each do |field|
948 950 add_custom_field_filter(field, assoc)
949 951 end
950 952 end
951 953
952 954 # Adds filters for the given associations custom fields
953 955 def add_associations_custom_fields_filters(*associations)
954 956 fields_by_class = CustomField.visible.where(:is_filter => true).group_by(&:class)
955 957 associations.each do |assoc|
956 958 association_klass = queried_class.reflect_on_association(assoc).klass
957 959 fields_by_class.each do |field_class, fields|
958 960 if field_class.customized_class <= association_klass
959 961 fields.sort.each do |field|
960 962 add_custom_field_filter(field, assoc)
961 963 end
962 964 end
963 965 end
964 966 end
965 967 end
966 968
967 969 def quoted_time(time, is_custom_filter)
968 970 if is_custom_filter
969 971 # Custom field values are stored as strings in the DB
970 972 # using this format that does not depend on DB date representation
971 973 time.strftime("%Y-%m-%d %H:%M:%S")
972 974 else
973 975 self.class.connection.quoted_date(time)
974 976 end
975 977 end
976 978
977 979 # Returns a SQL clause for a date or datetime field.
978 980 def date_clause(table, field, from, to, is_custom_filter)
979 981 s = []
980 982 if from
981 983 if from.is_a?(Date)
982 984 from = Time.local(from.year, from.month, from.day).yesterday.end_of_day
983 985 else
984 986 from = from - 1 # second
985 987 end
986 988 if self.class.default_timezone == :utc
987 989 from = from.utc
988 990 end
989 991 s << ("#{table}.#{field} > '%s'" % [quoted_time(from, is_custom_filter)])
990 992 end
991 993 if to
992 994 if to.is_a?(Date)
993 995 to = Time.local(to.year, to.month, to.day).end_of_day
994 996 end
995 997 if self.class.default_timezone == :utc
996 998 to = to.utc
997 999 end
998 1000 s << ("#{table}.#{field} <= '%s'" % [quoted_time(to, is_custom_filter)])
999 1001 end
1000 1002 s.join(' AND ')
1001 1003 end
1002 1004
1003 1005 # Returns a SQL clause for a date or datetime field using relative dates.
1004 1006 def relative_date_clause(table, field, days_from, days_to, is_custom_filter)
1005 1007 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil), is_custom_filter)
1006 1008 end
1007 1009
1008 1010 # Returns a Date or Time from the given filter value
1009 1011 def parse_date(arg)
1010 1012 if arg.to_s =~ /\A\d{4}-\d{2}-\d{2}T/
1011 1013 Time.parse(arg) rescue nil
1012 1014 else
1013 1015 Date.parse(arg) rescue nil
1014 1016 end
1015 1017 end
1016 1018
1017 1019 # Additional joins required for the given sort options
1018 1020 def joins_for_order_statement(order_options)
1019 1021 joins = []
1020 1022
1021 1023 if order_options
1022 1024 if order_options.include?('authors')
1023 1025 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{queried_table_name}.author_id"
1024 1026 end
1025 1027 order_options.scan(/cf_\d+/).uniq.each do |name|
1026 1028 column = available_columns.detect {|c| c.name.to_s == name}
1027 1029 join = column && column.custom_field.join_for_order_statement
1028 1030 if join
1029 1031 joins << join
1030 1032 end
1031 1033 end
1032 1034 end
1033 1035
1034 1036 joins.any? ? joins.join(' ') : nil
1035 1037 end
1036 1038 end
@@ -1,1186 +1,1188
1 1 en-GB:
2 2 direction: ltr
3 3 date:
4 4 formats:
5 5 # Use the strftime parameters for formats.
6 6 # When no format has been given, it uses default.
7 7 # You can provide other formats here if you like!
8 8 default: "%d/%m/%Y"
9 9 short: "%d %b"
10 10 long: "%d %B, %Y"
11 11
12 12 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
13 13 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
14 14
15 15 # Don't forget the nil at the beginning; there's no such thing as a 0th month
16 16 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
17 17 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
18 18 # Used in date_select and datime_select.
19 19 order:
20 20 - :year
21 21 - :month
22 22 - :day
23 23
24 24 time:
25 25 formats:
26 26 default: "%d/%m/%Y %I:%M %p"
27 27 time: "%I:%M %p"
28 28 short: "%d %b %H:%M"
29 29 long: "%d %B, %Y %H:%M"
30 30 am: "am"
31 31 pm: "pm"
32 32
33 33 datetime:
34 34 distance_in_words:
35 35 half_a_minute: "half a minute"
36 36 less_than_x_seconds:
37 37 one: "less than 1 second"
38 38 other: "less than %{count} seconds"
39 39 x_seconds:
40 40 one: "1 second"
41 41 other: "%{count} seconds"
42 42 less_than_x_minutes:
43 43 one: "less than a minute"
44 44 other: "less than %{count} minutes"
45 45 x_minutes:
46 46 one: "1 minute"
47 47 other: "%{count} minutes"
48 48 about_x_hours:
49 49 one: "about 1 hour"
50 50 other: "about %{count} hours"
51 51 x_hours:
52 52 one: "1 hour"
53 53 other: "%{count} hours"
54 54 x_days:
55 55 one: "1 day"
56 56 other: "%{count} days"
57 57 about_x_months:
58 58 one: "about 1 month"
59 59 other: "about %{count} months"
60 60 x_months:
61 61 one: "1 month"
62 62 other: "%{count} months"
63 63 about_x_years:
64 64 one: "about 1 year"
65 65 other: "about %{count} years"
66 66 over_x_years:
67 67 one: "over 1 year"
68 68 other: "over %{count} years"
69 69 almost_x_years:
70 70 one: "almost 1 year"
71 71 other: "almost %{count} years"
72 72
73 73 number:
74 74 format:
75 75 separator: "."
76 76 delimiter: " "
77 77 precision: 3
78 78
79 79 currency:
80 80 format:
81 81 format: "%u%n"
82 82 unit: "Β£"
83 83
84 84 human:
85 85 format:
86 86 delimiter: ""
87 87 precision: 3
88 88 storage_units:
89 89 format: "%n %u"
90 90 units:
91 91 byte:
92 92 one: "Byte"
93 93 other: "Bytes"
94 94 kb: "KB"
95 95 mb: "MB"
96 96 gb: "GB"
97 97 tb: "TB"
98 98
99 99 # Used in array.to_sentence.
100 100 support:
101 101 array:
102 102 sentence_connector: "and"
103 103 skip_last_comma: false
104 104
105 105 activerecord:
106 106 errors:
107 107 template:
108 108 header:
109 109 one: "1 error prohibited this %{model} from being saved"
110 110 other: "%{count} errors prohibited this %{model} from being saved"
111 111 messages:
112 112 inclusion: "is not included in the list"
113 113 exclusion: "is reserved"
114 114 invalid: "is invalid"
115 115 confirmation: "doesn't match confirmation"
116 116 accepted: "must be accepted"
117 117 empty: "cannot be empty"
118 118 blank: "cannot be blank"
119 119 too_long: "is too long (maximum is %{count} characters)"
120 120 too_short: "is too short (minimum is %{count} characters)"
121 121 wrong_length: "is the wrong length (should be %{count} characters)"
122 122 taken: "has already been taken"
123 123 not_a_number: "is not a number"
124 124 not_a_date: "is not a valid date"
125 125 greater_than: "must be greater than %{count}"
126 126 greater_than_or_equal_to: "must be greater than or equal to %{count}"
127 127 equal_to: "must be equal to %{count}"
128 128 less_than: "must be less than %{count}"
129 129 less_than_or_equal_to: "must be less than or equal to %{count}"
130 130 odd: "must be odd"
131 131 even: "must be even"
132 132 greater_than_start_date: "must be greater than start date"
133 133 not_same_project: "doesn't belong to the same project"
134 134 circular_dependency: "This relation would create a circular dependency"
135 135 cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks"
136 136 earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues"
137 137
138 138 actionview_instancetag_blank_option: Please select
139 139
140 140 general_text_No: 'No'
141 141 general_text_Yes: 'Yes'
142 142 general_text_no: 'no'
143 143 general_text_yes: 'yes'
144 144 general_lang_name: 'English (British)'
145 145 general_csv_separator: ','
146 146 general_csv_decimal_separator: '.'
147 147 general_csv_encoding: ISO-8859-1
148 148 general_pdf_fontname: freesans
149 149 general_first_day_of_week: '1'
150 150
151 151 notice_account_updated: Account was successfully updated.
152 152 notice_account_invalid_creditentials: Invalid user or password
153 153 notice_account_password_updated: Password was successfully updated.
154 154 notice_account_wrong_password: Wrong password
155 155 notice_account_register_done: Account was successfully created. An email containing the instructions to activate your account was sent to %{email}.
156 156 notice_account_unknown_email: Unknown user.
157 157 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
158 158 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
159 159 notice_account_activated: Your account has been activated. You can now log in.
160 160 notice_successful_create: Successful creation.
161 161 notice_successful_update: Successful update.
162 162 notice_successful_delete: Successful deletion.
163 163 notice_successful_connection: Successful connection.
164 164 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
165 165 notice_locking_conflict: Data has been updated by another user.
166 166 notice_not_authorized: You are not authorised to access this page.
167 167 notice_not_authorized_archived_project: The project you're trying to access has been archived.
168 168 notice_email_sent: "An email was sent to %{value}"
169 169 notice_email_error: "An error occurred while sending mail (%{value})"
170 170 notice_feeds_access_key_reseted: Your Atom access key was reset.
171 171 notice_api_access_key_reseted: Your API access key was reset.
172 172 notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}."
173 173 notice_failed_to_save_members: "Failed to save member(s): %{errors}."
174 174 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
175 175 notice_account_pending: "Your account was created and is now pending administrator approval."
176 176 notice_default_data_loaded: Default configuration successfully loaded.
177 177 notice_unable_delete_version: Unable to delete version.
178 178 notice_unable_delete_time_entry: Unable to delete time log entry.
179 179 notice_issue_done_ratios_updated: Issue done ratios updated.
180 180 notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
181 181
182 182 error_can_t_load_default_data: "Default configuration could not be loaded: %{value}"
183 183 error_scm_not_found: "The entry or revision was not found in the repository."
184 184 error_scm_command_failed: "An error occurred when trying to access the repository: %{value}"
185 185 error_scm_annotate: "The entry does not exist or cannot be annotated."
186 186 error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size."
187 187 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
188 188 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
189 189 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
190 190 error_can_not_delete_custom_field: Unable to delete custom field
191 191 error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted."
192 192 error_can_not_remove_role: "This role is in use and cannot be deleted."
193 193 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened'
194 194 error_can_not_archive_project: This project cannot be archived
195 195 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
196 196 error_workflow_copy_source: 'Please select a source tracker or role'
197 197 error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
198 198 error_unable_delete_issue_status: 'Unable to delete issue status'
199 199 error_unable_to_connect: "Unable to connect (%{value})"
200 200 warning_attachments_not_saved: "%{count} file(s) could not be saved."
201 201
202 202 mail_subject_lost_password: "Your %{value} password"
203 203 mail_body_lost_password: 'To change your password, click on the following link:'
204 204 mail_subject_register: "Your %{value} account activation"
205 205 mail_body_register: 'To activate your account, click on the following link:'
206 206 mail_body_account_information_external: "You can use your %{value} account to log in."
207 207 mail_body_account_information: Your account information
208 208 mail_subject_account_activation_request: "%{value} account activation request"
209 209 mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:"
210 210 mail_subject_reminder: "%{count} issue(s) due in the next %{days} days"
211 211 mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:"
212 212 mail_subject_wiki_content_added: "'%{id}' wiki page has been added"
213 213 mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}."
214 214 mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated"
215 215 mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}."
216 216
217 217
218 218 field_name: Name
219 219 field_description: Description
220 220 field_summary: Summary
221 221 field_is_required: Required
222 222 field_firstname: First name
223 223 field_lastname: Last name
224 224 field_mail: Email
225 225 field_filename: File
226 226 field_filesize: Size
227 227 field_downloads: Downloads
228 228 field_author: Author
229 229 field_created_on: Created
230 230 field_updated_on: Updated
231 231 field_field_format: Format
232 232 field_is_for_all: For all projects
233 233 field_possible_values: Possible values
234 234 field_regexp: Regular expression
235 235 field_min_length: Minimum length
236 236 field_max_length: Maximum length
237 237 field_value: Value
238 238 field_category: Category
239 239 field_title: Title
240 240 field_project: Project
241 241 field_issue: Issue
242 242 field_status: Status
243 243 field_notes: Notes
244 244 field_is_closed: Issue closed
245 245 field_is_default: Default value
246 246 field_tracker: Tracker
247 247 field_subject: Subject
248 248 field_due_date: Due date
249 249 field_assigned_to: Assignee
250 250 field_priority: Priority
251 251 field_fixed_version: Target version
252 252 field_user: User
253 253 field_principal: Principal
254 254 field_role: Role
255 255 field_homepage: Homepage
256 256 field_is_public: Public
257 257 field_parent: Subproject of
258 258 field_is_in_roadmap: Issues displayed in roadmap
259 259 field_login: Login
260 260 field_mail_notification: Email notifications
261 261 field_admin: Administrator
262 262 field_last_login_on: Last connection
263 263 field_language: Language
264 264 field_effective_date: Date
265 265 field_password: Password
266 266 field_new_password: New password
267 267 field_password_confirmation: Confirmation
268 268 field_version: Version
269 269 field_type: Type
270 270 field_host: Host
271 271 field_port: Port
272 272 field_account: Account
273 273 field_base_dn: Base DN
274 274 field_attr_login: Login attribute
275 275 field_attr_firstname: Firstname attribute
276 276 field_attr_lastname: Lastname attribute
277 277 field_attr_mail: Email attribute
278 278 field_onthefly: On-the-fly user creation
279 279 field_start_date: Start date
280 280 field_done_ratio: "% Done"
281 281 field_auth_source: Authentication mode
282 282 field_hide_mail: Hide my email address
283 283 field_comments: Comment
284 284 field_url: URL
285 285 field_start_page: Start page
286 286 field_subproject: Subproject
287 287 field_hours: Hours
288 288 field_activity: Activity
289 289 field_spent_on: Date
290 290 field_identifier: Identifier
291 291 field_is_filter: Used as a filter
292 292 field_issue_to: Related issue
293 293 field_delay: Delay
294 294 field_assignable: Issues can be assigned to this role
295 295 field_redirect_existing_links: Redirect existing links
296 296 field_estimated_hours: Estimated time
297 297 field_column_names: Columns
298 298 field_time_entries: Log time
299 299 field_time_zone: Time zone
300 300 field_searchable: Searchable
301 301 field_default_value: Default value
302 302 field_comments_sorting: Display comments
303 303 field_parent_title: Parent page
304 304 field_editable: Editable
305 305 field_watcher: Watcher
306 306 field_identity_url: OpenID URL
307 307 field_content: Content
308 308 field_group_by: Group results by
309 309 field_sharing: Sharing
310 310 field_parent_issue: Parent task
311 311 field_member_of_group: "Assignee's group"
312 312 field_assigned_to_role: "Assignee's role"
313 313 field_text: Text field
314 314 field_visible: Visible
315 315 field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
316 316
317 317 setting_app_title: Application title
318 318 setting_app_subtitle: Application subtitle
319 319 setting_welcome_text: Welcome text
320 320 setting_default_language: Default language
321 321 setting_login_required: Authentication required
322 322 setting_self_registration: Self-registration
323 323 setting_attachment_max_size: Attachment max. size
324 324 setting_issues_export_limit: Issues export limit
325 325 setting_mail_from: Emission email address
326 326 setting_bcc_recipients: Blind carbon copy recipients (bcc)
327 327 setting_plain_text_mail: Plain text mail (no HTML)
328 328 setting_host_name: Host name and path
329 329 setting_text_formatting: Text formatting
330 330 setting_wiki_compression: Wiki history compression
331 331 setting_feeds_limit: Feed content limit
332 332 setting_default_projects_public: New projects are public by default
333 333 setting_autofetch_changesets: Autofetch commits
334 334 setting_sys_api_enabled: Enable WS for repository management
335 335 setting_commit_ref_keywords: Referencing keywords
336 336 setting_commit_fix_keywords: Fixing keywords
337 337 setting_autologin: Autologin
338 338 setting_date_format: Date format
339 339 setting_time_format: Time format
340 340 setting_cross_project_issue_relations: Allow cross-project issue relations
341 341 setting_issue_list_default_columns: Default columns displayed on the issue list
342 342 setting_emails_header: Email header
343 343 setting_emails_footer: Email footer
344 344 setting_protocol: Protocol
345 345 setting_per_page_options: Objects per page options
346 346 setting_user_format: Users display format
347 347 setting_activity_days_default: Days displayed on project activity
348 348 setting_display_subprojects_issues: Display subprojects issues on main projects by default
349 349 setting_enabled_scm: Enabled SCM
350 350 setting_mail_handler_body_delimiters: "Truncate emails after one of these lines"
351 351 setting_mail_handler_api_enabled: Enable WS for incoming emails
352 352 setting_mail_handler_api_key: API key
353 353 setting_sequential_project_identifiers: Generate sequential project identifiers
354 354 setting_gravatar_enabled: Use Gravatar user icons
355 355 setting_gravatar_default: Default Gravatar image
356 356 setting_diff_max_lines_displayed: Max number of diff lines displayed
357 357 setting_file_max_size_displayed: Max size of text files displayed inline
358 358 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
359 359 setting_openid: Allow OpenID login and registration
360 360 setting_password_min_length: Minimum password length
361 361 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
362 362 setting_default_projects_modules: Default enabled modules for new projects
363 363 setting_issue_done_ratio: Calculate the issue done ratio with
364 364 setting_issue_done_ratio_issue_field: Use the issue field
365 365 setting_issue_done_ratio_issue_status: Use the issue status
366 366 setting_start_of_week: Start calendars on
367 367 setting_rest_api_enabled: Enable REST web service
368 368 setting_cache_formatted_text: Cache formatted text
369 369 setting_default_notification_option: Default notification option
370 370 setting_commit_logtime_enabled: Enable time logging
371 371 setting_commit_logtime_activity_id: Activity for logged time
372 372 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
373 373 setting_issue_group_assignment: Allow issue assignment to groups
374 374 setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
375 375
376 376 permission_add_project: Create project
377 377 permission_add_subprojects: Create subprojects
378 378 permission_edit_project: Edit project
379 379 permission_select_project_modules: Select project modules
380 380 permission_manage_members: Manage members
381 381 permission_manage_project_activities: Manage project activities
382 382 permission_manage_versions: Manage versions
383 383 permission_manage_categories: Manage issue categories
384 384 permission_view_issues: View Issues
385 385 permission_add_issues: Add issues
386 386 permission_edit_issues: Edit issues
387 387 permission_manage_issue_relations: Manage issue relations
388 388 permission_add_issue_notes: Add notes
389 389 permission_edit_issue_notes: Edit notes
390 390 permission_edit_own_issue_notes: Edit own notes
391 391 permission_move_issues: Move issues
392 392 permission_delete_issues: Delete issues
393 393 permission_manage_public_queries: Manage public queries
394 394 permission_save_queries: Save queries
395 395 permission_view_gantt: View gantt chart
396 396 permission_view_calendar: View calendar
397 397 permission_view_issue_watchers: View watchers list
398 398 permission_add_issue_watchers: Add watchers
399 399 permission_delete_issue_watchers: Delete watchers
400 400 permission_log_time: Log spent time
401 401 permission_view_time_entries: View spent time
402 402 permission_edit_time_entries: Edit time logs
403 403 permission_edit_own_time_entries: Edit own time logs
404 404 permission_manage_news: Manage news
405 405 permission_comment_news: Comment news
406 406 permission_view_documents: View documents
407 407 permission_manage_files: Manage files
408 408 permission_view_files: View files
409 409 permission_manage_wiki: Manage wiki
410 410 permission_rename_wiki_pages: Rename wiki pages
411 411 permission_delete_wiki_pages: Delete wiki pages
412 412 permission_view_wiki_pages: View wiki
413 413 permission_view_wiki_edits: View wiki history
414 414 permission_edit_wiki_pages: Edit wiki pages
415 415 permission_delete_wiki_pages_attachments: Delete attachments
416 416 permission_protect_wiki_pages: Protect wiki pages
417 417 permission_manage_repository: Manage repository
418 418 permission_browse_repository: Browse repository
419 419 permission_view_changesets: View changesets
420 420 permission_commit_access: Commit access
421 421 permission_manage_boards: Manage forums
422 422 permission_view_messages: View messages
423 423 permission_add_messages: Post messages
424 424 permission_edit_messages: Edit messages
425 425 permission_edit_own_messages: Edit own messages
426 426 permission_delete_messages: Delete messages
427 427 permission_delete_own_messages: Delete own messages
428 428 permission_export_wiki_pages: Export wiki pages
429 429 permission_manage_subtasks: Manage subtasks
430 430
431 431 project_module_issue_tracking: Issue tracking
432 432 project_module_time_tracking: Time tracking
433 433 project_module_news: News
434 434 project_module_documents: Documents
435 435 project_module_files: Files
436 436 project_module_wiki: Wiki
437 437 project_module_repository: Repository
438 438 project_module_boards: Forums
439 439 project_module_calendar: Calendar
440 440 project_module_gantt: Gantt
441 441
442 442 label_user: User
443 443 label_user_plural: Users
444 444 label_user_new: New user
445 445 label_user_anonymous: Anonymous
446 446 label_project: Project
447 447 label_project_new: New project
448 448 label_project_plural: Projects
449 449 label_x_projects:
450 450 zero: no projects
451 451 one: 1 project
452 452 other: "%{count} projects"
453 453 label_project_all: All Projects
454 454 label_project_latest: Latest projects
455 455 label_issue: Issue
456 456 label_issue_new: New issue
457 457 label_issue_plural: Issues
458 458 label_issue_view_all: View all issues
459 459 label_issues_by: "Issues by %{value}"
460 460 label_issue_added: Issue added
461 461 label_issue_updated: Issue updated
462 462 label_document: Document
463 463 label_document_new: New document
464 464 label_document_plural: Documents
465 465 label_document_added: Document added
466 466 label_role: Role
467 467 label_role_plural: Roles
468 468 label_role_new: New role
469 469 label_role_and_permissions: Roles and permissions
470 470 label_role_anonymous: Anonymous
471 471 label_role_non_member: Non member
472 472 label_member: Member
473 473 label_member_new: New member
474 474 label_member_plural: Members
475 475 label_tracker: Tracker
476 476 label_tracker_plural: Trackers
477 477 label_tracker_new: New tracker
478 478 label_workflow: Workflow
479 479 label_issue_status: Issue status
480 480 label_issue_status_plural: Issue statuses
481 481 label_issue_status_new: New status
482 482 label_issue_category: Issue category
483 483 label_issue_category_plural: Issue categories
484 484 label_issue_category_new: New category
485 485 label_custom_field: Custom field
486 486 label_custom_field_plural: Custom fields
487 487 label_custom_field_new: New custom field
488 488 label_enumerations: Enumerations
489 489 label_enumeration_new: New value
490 490 label_information: Information
491 491 label_information_plural: Information
492 492 label_please_login: Please log in
493 493 label_register: Register
494 494 label_login_with_open_id_option: or login with OpenID
495 495 label_password_lost: Lost password
496 496 label_home: Home
497 497 label_my_page: My page
498 498 label_my_account: My account
499 499 label_my_projects: My projects
500 500 label_my_page_block: My page block
501 501 label_administration: Administration
502 502 label_login: Sign in
503 503 label_logout: Sign out
504 504 label_help: Help
505 505 label_reported_issues: Reported issues
506 506 label_assigned_to_me_issues: Issues assigned to me
507 507 label_last_login: Last connection
508 508 label_registered_on: Registered on
509 509 label_activity: Activity
510 510 label_overall_activity: Overall activity
511 511 label_user_activity: "%{value}'s activity"
512 512 label_new: New
513 513 label_logged_as: Logged in as
514 514 label_environment: Environment
515 515 label_authentication: Authentication
516 516 label_auth_source: Authentication mode
517 517 label_auth_source_new: New authentication mode
518 518 label_auth_source_plural: Authentication modes
519 519 label_subproject_plural: Subprojects
520 520 label_subproject_new: New subproject
521 521 label_and_its_subprojects: "%{value} and its subprojects"
522 522 label_min_max_length: Min - Max length
523 523 label_list: List
524 524 label_date: Date
525 525 label_integer: Integer
526 526 label_float: Float
527 527 label_boolean: Boolean
528 528 label_string: Text
529 529 label_text: Long text
530 530 label_attribute: Attribute
531 531 label_attribute_plural: Attributes
532 532 label_no_data: No data to display
533 533 label_change_status: Change status
534 534 label_history: History
535 535 label_attachment: File
536 536 label_attachment_new: New file
537 537 label_attachment_delete: Delete file
538 538 label_attachment_plural: Files
539 539 label_file_added: File added
540 540 label_report: Report
541 541 label_report_plural: Reports
542 542 label_news: News
543 543 label_news_new: Add news
544 544 label_news_plural: News
545 545 label_news_latest: Latest news
546 546 label_news_view_all: View all news
547 547 label_news_added: News added
548 548 label_news_comment_added: Comment added to a news
549 549 label_settings: Settings
550 550 label_overview: Overview
551 551 label_version: Version
552 552 label_version_new: New version
553 553 label_version_plural: Versions
554 554 label_close_versions: Close completed versions
555 555 label_confirmation: Confirmation
556 556 label_export_to: 'Also available in:'
557 557 label_read: Read...
558 558 label_public_projects: Public projects
559 559 label_open_issues: open
560 560 label_open_issues_plural: open
561 561 label_closed_issues: closed
562 562 label_closed_issues_plural: closed
563 563 label_x_open_issues_abbr:
564 564 zero: 0 open
565 565 one: 1 open
566 566 other: "%{count} open"
567 567 label_x_closed_issues_abbr:
568 568 zero: 0 closed
569 569 one: 1 closed
570 570 other: "%{count} closed"
571 571 label_total: Total
572 572 label_permissions: Permissions
573 573 label_current_status: Current status
574 574 label_new_statuses_allowed: New statuses allowed
575 575 label_all: all
576 576 label_none: none
577 577 label_nobody: nobody
578 578 label_next: Next
579 579 label_previous: Previous
580 580 label_used_by: Used by
581 581 label_details: Details
582 582 label_add_note: Add a note
583 583 label_calendar: Calendar
584 584 label_months_from: months from
585 585 label_gantt: Gantt
586 586 label_internal: Internal
587 587 label_last_changes: "last %{count} changes"
588 588 label_change_view_all: View all changes
589 589 label_personalize_page: Personalise this page
590 590 label_comment: Comment
591 591 label_comment_plural: Comments
592 592 label_x_comments:
593 593 zero: no comments
594 594 one: 1 comment
595 595 other: "%{count} comments"
596 596 label_comment_add: Add a comment
597 597 label_comment_added: Comment added
598 598 label_comment_delete: Delete comments
599 599 label_query: Custom query
600 600 label_query_plural: Custom queries
601 601 label_query_new: New query
602 602 label_my_queries: My custom queries
603 603 label_filter_add: Add filter
604 604 label_filter_plural: Filters
605 605 label_equals: is
606 606 label_not_equals: is not
607 607 label_in_less_than: in less than
608 608 label_in_more_than: in more than
609 609 label_greater_or_equal: '>='
610 610 label_less_or_equal: '<='
611 611 label_in: in
612 612 label_today: today
613 613 label_all_time: all time
614 614 label_yesterday: yesterday
615 615 label_this_week: this week
616 616 label_last_week: last week
617 617 label_last_n_days: "last %{count} days"
618 618 label_this_month: this month
619 619 label_last_month: last month
620 620 label_this_year: this year
621 621 label_date_range: Date range
622 622 label_less_than_ago: less than days ago
623 623 label_more_than_ago: more than days ago
624 624 label_ago: days ago
625 625 label_contains: contains
626 626 label_not_contains: doesn't contain
627 627 label_day_plural: days
628 628 label_repository: Repository
629 629 label_repository_plural: Repositories
630 630 label_browse: Browse
631 631 label_branch: Branch
632 632 label_tag: Tag
633 633 label_revision: Revision
634 634 label_revision_plural: Revisions
635 635 label_revision_id: "Revision %{value}"
636 636 label_associated_revisions: Associated revisions
637 637 label_added: added
638 638 label_modified: modified
639 639 label_copied: copied
640 640 label_renamed: renamed
641 641 label_deleted: deleted
642 642 label_latest_revision: Latest revision
643 643 label_latest_revision_plural: Latest revisions
644 644 label_view_revisions: View revisions
645 645 label_view_all_revisions: View all revisions
646 646 label_max_size: Maximum size
647 647 label_sort_highest: Move to top
648 648 label_sort_higher: Move up
649 649 label_sort_lower: Move down
650 650 label_sort_lowest: Move to bottom
651 651 label_roadmap: Roadmap
652 652 label_roadmap_due_in: "Due in %{value}"
653 653 label_roadmap_overdue: "%{value} late"
654 654 label_roadmap_no_issues: No issues for this version
655 655 label_search: Search
656 656 label_result_plural: Results
657 657 label_all_words: All words
658 658 label_wiki: Wiki
659 659 label_wiki_edit: Wiki edit
660 660 label_wiki_edit_plural: Wiki edits
661 661 label_wiki_page: Wiki page
662 662 label_wiki_page_plural: Wiki pages
663 663 label_index_by_title: Index by title
664 664 label_index_by_date: Index by date
665 665 label_current_version: Current version
666 666 label_preview: Preview
667 667 label_feed_plural: Feeds
668 668 label_changes_details: Details of all changes
669 669 label_issue_tracking: Issue tracking
670 670 label_spent_time: Spent time
671 671 label_overall_spent_time: Overall spent time
672 672 label_f_hour: "%{value} hour"
673 673 label_f_hour_plural: "%{value} hours"
674 674 label_time_tracking: Time tracking
675 675 label_change_plural: Changes
676 676 label_statistics: Statistics
677 677 label_commits_per_month: Commits per month
678 678 label_commits_per_author: Commits per author
679 679 label_view_diff: View differences
680 680 label_diff_inline: inline
681 681 label_diff_side_by_side: side by side
682 682 label_options: Options
683 683 label_copy_workflow_from: Copy workflow from
684 684 label_permissions_report: Permissions report
685 685 label_watched_issues: Watched issues
686 686 label_related_issues: Related issues
687 687 label_applied_status: Applied status
688 688 label_loading: Loading...
689 689 label_relation_new: New relation
690 690 label_relation_delete: Delete relation
691 691 label_relates_to: related to
692 692 label_duplicates: duplicates
693 693 label_duplicated_by: duplicated by
694 694 label_blocks: blocks
695 695 label_blocked_by: blocked by
696 696 label_precedes: precedes
697 697 label_follows: follows
698 698 label_end_to_start: end to start
699 699 label_end_to_end: end to end
700 700 label_start_to_start: start to start
701 701 label_start_to_end: start to end
702 702 label_stay_logged_in: Stay logged in
703 703 label_disabled: disabled
704 704 label_show_completed_versions: Show completed versions
705 705 label_me: me
706 706 label_board: Forum
707 707 label_board_new: New forum
708 708 label_board_plural: Forums
709 709 label_board_locked: Locked
710 710 label_board_sticky: Sticky
711 711 label_topic_plural: Topics
712 712 label_message_plural: Messages
713 713 label_message_last: Last message
714 714 label_message_new: New message
715 715 label_message_posted: Message added
716 716 label_reply_plural: Replies
717 717 label_send_information: Send account information to the user
718 718 label_year: Year
719 719 label_month: Month
720 720 label_week: Week
721 721 label_date_from: From
722 722 label_date_to: To
723 723 label_language_based: Based on user's language
724 724 label_sort_by: "Sort by %{value}"
725 725 label_send_test_email: Send a test email
726 726 label_feeds_access_key: Atom access key
727 727 label_missing_feeds_access_key: Missing a Atom access key
728 728 label_feeds_access_key_created_on: "Atom access key created %{value} ago"
729 729 label_module_plural: Modules
730 730 label_added_time_by: "Added by %{author} %{age} ago"
731 731 label_updated_time_by: "Updated by %{author} %{age} ago"
732 732 label_updated_time: "Updated %{value} ago"
733 733 label_jump_to_a_project: Jump to a project...
734 734 label_file_plural: Files
735 735 label_changeset_plural: Changesets
736 736 label_default_columns: Default columns
737 737 label_no_change_option: (No change)
738 738 label_bulk_edit_selected_issues: Bulk edit selected issues
739 739 label_theme: Theme
740 740 label_default: Default
741 741 label_search_titles_only: Search titles only
742 742 label_user_mail_option_all: "For any event on all my projects"
743 743 label_user_mail_option_selected: "For any event on the selected projects only..."
744 744 label_user_mail_option_none: "No events"
745 745 label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in"
746 746 label_user_mail_option_only_assigned: "Only for things I am assigned to"
747 747 label_user_mail_option_only_owner: "Only for things I am the owner of"
748 748 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
749 749 label_registration_activation_by_email: account activation by email
750 750 label_registration_manual_activation: manual account activation
751 751 label_registration_automatic_activation: automatic account activation
752 752 label_display_per_page: "Per page: %{value}"
753 753 label_age: Age
754 754 label_change_properties: Change properties
755 755 label_general: General
756 756 label_more: More
757 757 label_scm: SCM
758 758 label_plugins: Plugins
759 759 label_ldap_authentication: LDAP authentication
760 760 label_downloads_abbr: D/L
761 761 label_optional_description: Optional description
762 762 label_add_another_file: Add another file
763 763 label_preferences: Preferences
764 764 label_chronological_order: In chronological order
765 765 label_reverse_chronological_order: In reverse chronological order
766 766 label_planning: Planning
767 767 label_incoming_emails: Incoming emails
768 768 label_generate_key: Generate a key
769 769 label_issue_watchers: Watchers
770 770 label_example: Example
771 771 label_display: Display
772 772 label_sort: Sort
773 773 label_ascending: Ascending
774 774 label_descending: Descending
775 775 label_date_from_to: From %{start} to %{end}
776 776 label_wiki_content_added: Wiki page added
777 777 label_wiki_content_updated: Wiki page updated
778 778 label_group: Group
779 779 label_group_plural: Groups
780 780 label_group_new: New group
781 781 label_time_entry_plural: Spent time
782 782 label_version_sharing_none: Not shared
783 783 label_version_sharing_descendants: With subprojects
784 784 label_version_sharing_hierarchy: With project hierarchy
785 785 label_version_sharing_tree: With project tree
786 786 label_version_sharing_system: With all projects
787 787 label_update_issue_done_ratios: Update issue done ratios
788 788 label_copy_source: Source
789 789 label_copy_target: Target
790 790 label_copy_same_as_target: Same as target
791 791 label_display_used_statuses_only: Only display statuses that are used by this tracker
792 792 label_api_access_key: API access key
793 793 label_missing_api_access_key: Missing an API access key
794 794 label_api_access_key_created_on: "API access key created %{value} ago"
795 795 label_profile: Profile
796 796 label_subtask_plural: Subtasks
797 797 label_project_copy_notifications: Send email notifications during the project copy
798 798 label_principal_search: "Search for user or group:"
799 799 label_user_search: "Search for user:"
800 800
801 801 button_login: Login
802 802 button_submit: Submit
803 803 button_save: Save
804 804 button_check_all: Check all
805 805 button_uncheck_all: Uncheck all
806 806 button_collapse_all: Collapse all
807 807 button_expand_all: Expand all
808 808 button_delete: Delete
809 809 button_create: Create
810 810 button_create_and_continue: Create and continue
811 811 button_test: Test
812 812 button_edit: Edit
813 813 button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
814 814 button_add: Add
815 815 button_change: Change
816 816 button_apply: Apply
817 817 button_clear: Clear
818 818 button_lock: Lock
819 819 button_unlock: Unlock
820 820 button_download: Download
821 821 button_list: List
822 822 button_view: View
823 823 button_move: Move
824 824 button_move_and_follow: Move and follow
825 825 button_back: Back
826 826 button_cancel: Cancel
827 827 button_activate: Activate
828 828 button_sort: Sort
829 829 button_log_time: Log time
830 830 button_rollback: Rollback to this version
831 831 button_watch: Watch
832 832 button_unwatch: Unwatch
833 833 button_reply: Reply
834 834 button_archive: Archive
835 835 button_unarchive: Unarchive
836 836 button_reset: Reset
837 837 button_rename: Rename
838 838 button_change_password: Change password
839 839 button_copy: Copy
840 840 button_copy_and_follow: Copy and follow
841 841 button_annotate: Annotate
842 842 button_update: Update
843 843 button_configure: Configure
844 844 button_quote: Quote
845 845 button_duplicate: Duplicate
846 846 button_show: Show
847 847
848 848 status_active: active
849 849 status_registered: registered
850 850 status_locked: locked
851 851
852 852 version_status_open: open
853 853 version_status_locked: locked
854 854 version_status_closed: closed
855 855
856 856 field_active: Active
857 857
858 858 text_select_mail_notifications: Select actions for which email notifications should be sent.
859 859 text_regexp_info: eg. ^[A-Z0-9]+$
860 860 text_min_max_length_info: 0 means no restriction
861 861 text_project_destroy_confirmation: Are you sure you want to delete this project and related data?
862 862 text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
863 863 text_workflow_edit: Select a role and a tracker to edit the workflow
864 864 text_are_you_sure: Are you sure?
865 865 text_journal_changed: "%{label} changed from %{old} to %{new}"
866 866 text_journal_changed_no_detail: "%{label} updated"
867 867 text_journal_set_to: "%{label} set to %{value}"
868 868 text_journal_deleted: "%{label} deleted (%{old})"
869 869 text_journal_added: "%{label} %{value} added"
870 870 text_tip_issue_begin_day: task beginning this day
871 871 text_tip_issue_end_day: task ending this day
872 872 text_tip_issue_begin_end_day: task beginning and ending this day
873 873 text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed, must start with a lower case letter.<br />Once saved, the identifier cannot be changed.'
874 874 text_caracters_maximum: "%{count} characters maximum."
875 875 text_caracters_minimum: "Must be at least %{count} characters long."
876 876 text_length_between: "Length between %{min} and %{max} characters."
877 877 text_tracker_no_workflow: No workflow defined for this tracker
878 878 text_unallowed_characters: Unallowed characters
879 879 text_comma_separated: Multiple values allowed (comma separated).
880 880 text_line_separated: Multiple values allowed (one line for each value).
881 881 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
882 882 text_issue_added: "Issue %{id} has been reported by %{author}."
883 883 text_issue_updated: "Issue %{id} has been updated by %{author}."
884 884 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content?
885 885 text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?"
886 886 text_issue_category_destroy_assignments: Remove category assignments
887 887 text_issue_category_reassign_to: Reassign issues to this category
888 888 text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)."
889 889 text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded."
890 890 text_load_default_configuration: Load the default configuration
891 891 text_status_changed_by_changeset: "Applied in changeset %{value}."
892 892 text_time_logged_by_changeset: "Applied in changeset %{value}."
893 893 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
894 894 text_select_project_modules: 'Select modules to enable for this project:'
895 895 text_default_administrator_account_changed: Default administrator account changed
896 896 text_file_repository_writable: Attachments directory writable
897 897 text_plugin_assets_writable: Plugin assets directory writable
898 898 text_rmagick_available: RMagick available (optional)
899 899 text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?"
900 900 text_destroy_time_entries: Delete reported hours
901 901 text_assign_time_entries_to_project: Assign reported hours to the project
902 902 text_reassign_time_entries: 'Reassign reported hours to this issue:'
903 903 text_user_wrote: "%{value} wrote:"
904 904 text_enumeration_destroy_question: "%{count} objects are assigned to this value."
905 905 text_enumeration_category_reassign_to: 'Reassign them to this value:'
906 906 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them."
907 907 text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
908 908 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
909 909 text_custom_field_possible_values_info: 'One line for each value'
910 910 text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?"
911 911 text_wiki_page_nullify_children: "Keep child pages as root pages"
912 912 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
913 913 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
914 914 text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?"
915 915 text_zoom_in: Zoom in
916 916 text_zoom_out: Zoom out
917 917 text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page."
918 918
919 919 default_role_manager: Manager
920 920 default_role_developer: Developer
921 921 default_role_reporter: Reporter
922 922 default_tracker_bug: Bug
923 923 default_tracker_feature: Feature
924 924 default_tracker_support: Support
925 925 default_issue_status_new: New
926 926 default_issue_status_in_progress: In Progress
927 927 default_issue_status_resolved: Resolved
928 928 default_issue_status_feedback: Feedback
929 929 default_issue_status_closed: Closed
930 930 default_issue_status_rejected: Rejected
931 931 default_doc_category_user: User documentation
932 932 default_doc_category_tech: Technical documentation
933 933 default_priority_low: Low
934 934 default_priority_normal: Normal
935 935 default_priority_high: High
936 936 default_priority_urgent: Urgent
937 937 default_priority_immediate: Immediate
938 938 default_activity_design: Design
939 939 default_activity_development: Development
940 940
941 941 enumeration_issue_priorities: Issue priorities
942 942 enumeration_doc_categories: Document categories
943 943 enumeration_activities: Activities (time tracking)
944 944 enumeration_system_activity: System Activity
945 945 label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
946 946 label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
947 947 label_bulk_edit_selected_time_entries: Bulk edit selected time entries
948 948 text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)?
949 949 label_issue_note_added: Note added
950 950 label_issue_status_updated: Status updated
951 951 label_issue_priority_updated: Priority updated
952 952 label_issues_visibility_own: Issues created by or assigned to the user
953 953 field_issues_visibility: Issues visibility
954 954 label_issues_visibility_all: All issues
955 955 permission_set_own_issues_private: Set own issues public or private
956 956 field_is_private: Private
957 957 permission_set_issues_private: Set issues public or private
958 958 label_issues_visibility_public: All non private issues
959 959 text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s).
960 960 field_commit_logs_encoding: Commit messages encoding
961 961 field_scm_path_encoding: Path encoding
962 962 text_scm_path_encoding_note: "Default: UTF-8"
963 963 field_path_to_repository: Path to repository
964 964 field_root_directory: Root directory
965 965 field_cvs_module: Module
966 966 field_cvsroot: CVSROOT
967 967 text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo)
968 968 text_scm_command: Command
969 969 text_scm_command_version: Version
970 970 label_git_report_last_commit: Report last commit for files and directories
971 971 text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it.
972 972 text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel.
973 973 notice_issue_successful_create: Issue %{id} created.
974 974 label_between: between
975 975 label_diff: diff
976 976 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
977 977 description_query_sort_criteria_direction: Sort direction
978 978 description_project_scope: Search scope
979 979 description_filter: Filter
980 980 description_user_mail_notification: Mail notification settings
981 981 description_date_from: Enter start date
982 982 description_message_content: Message content
983 983 description_available_columns: Available Columns
984 984 description_date_range_interval: Choose range by selecting start and end date
985 985 description_issue_category_reassign: Choose issue category
986 986 description_search: Searchfield
987 987 description_notes: Notes
988 988 description_date_range_list: Choose range from list
989 989 description_choose_project: Projects
990 990 description_date_to: Enter end date
991 991 description_query_sort_criteria_attribute: Sort attribute
992 992 description_wiki_subpages_reassign: Choose new parent page
993 993 description_selected_columns: Selected Columns
994 994 label_parent_revision: Parent
995 995 label_child_revision: Child
996 996 button_edit_section: Edit this section
997 997 setting_repositories_encodings: Attachments and repositories encodings
998 998 description_all_columns: All Columns
999 999 button_export: Export
1000 1000 label_export_options: "%{export_format} export options"
1001 1001 error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})
1002 1002 notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
1003 1003 label_x_issues:
1004 1004 zero: 0 issue
1005 1005 one: 1 issue
1006 1006 other: "%{count} issues"
1007 1007 label_repository_new: New repository
1008 1008 field_repository_is_default: Main repository
1009 1009 label_copy_attachments: Copy attachments
1010 1010 label_item_position: "%{position} of %{count}"
1011 1011 label_completed_versions: Completed versions
1012 1012 field_multiple: Multiple values
1013 1013 setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed
1014 1014 text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes
1015 1015 text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten)
1016 1016 notice_issue_update_conflict: The issue has been updated by an other user while you were editing it.
1017 1017 text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link}
1018 1018 permission_manage_related_issues: Manage related issues
1019 1019 field_auth_source_ldap_filter: LDAP filter
1020 1020 label_search_for_watchers: Search for watchers to add
1021 1021 notice_account_deleted: Your account has been permanently deleted.
1022 1022 setting_unsubscribe: Allow users to delete their own account
1023 1023 button_delete_my_account: Delete my account
1024 1024 text_account_destroy_confirmation: |-
1025 1025 Are you sure you want to proceed?
1026 1026 Your account will be permanently deleted, with no way to reactivate it.
1027 1027 error_session_expired: Your session has expired. Please login again.
1028 1028 text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours."
1029 1029 setting_session_lifetime: Session maximum lifetime
1030 1030 setting_session_timeout: Session inactivity timeout
1031 1031 label_session_expiration: Session expiration
1032 1032 permission_close_project: Close / reopen the project
1033 1033 label_show_closed_projects: View closed projects
1034 1034 button_close: Close
1035 1035 button_reopen: Reopen
1036 1036 project_status_active: active
1037 1037 project_status_closed: closed
1038 1038 project_status_archived: archived
1039 1039 text_project_closed: This project is closed and read-only.
1040 1040 notice_user_successful_create: User %{id} created.
1041 1041 field_core_fields: Standard fields
1042 1042 field_timeout: Timeout (in seconds)
1043 1043 setting_thumbnails_enabled: Display attachment thumbnails
1044 1044 setting_thumbnails_size: Thumbnails size (in pixels)
1045 1045 label_status_transitions: Status transitions
1046 1046 label_fields_permissions: Fields permissions
1047 1047 label_readonly: Read-only
1048 1048 label_required: Required
1049 1049 text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
1050 1050 field_board_parent: Parent forum
1051 1051 label_attribute_of_project: Project's %{name}
1052 1052 label_attribute_of_author: Author's %{name}
1053 1053 label_attribute_of_assigned_to: Assignee's %{name}
1054 1054 label_attribute_of_fixed_version: Target version's %{name}
1055 1055 label_copy_subtasks: Copy subtasks
1056 1056 label_copied_to: copied to
1057 1057 label_copied_from: copied from
1058 1058 label_any_issues_in_project: any issues in project
1059 1059 label_any_issues_not_in_project: any issues not in project
1060 1060 field_private_notes: Private notes
1061 1061 permission_view_private_notes: View private notes
1062 1062 permission_set_notes_private: Set notes as private
1063 1063 label_no_issues_in_project: no issues in project
1064 label_any_open_issues: any open issues
1065 label_no_open_issues: no open issues
1064 1066 label_any: all
1065 1067 label_last_n_weeks: last %{count} weeks
1066 1068 setting_cross_project_subtasks: Allow cross-project subtasks
1067 1069 label_cross_project_descendants: With subprojects
1068 1070 label_cross_project_tree: With project tree
1069 1071 label_cross_project_hierarchy: With project hierarchy
1070 1072 label_cross_project_system: With all projects
1071 1073 button_hide: Hide
1072 1074 setting_non_working_week_days: Non-working days
1073 1075 label_in_the_next_days: in the next
1074 1076 label_in_the_past_days: in the past
1075 1077 label_attribute_of_user: User's %{name}
1076 1078 text_turning_multiple_off: If you disable multiple values, multiple values will be
1077 1079 removed in order to preserve only one value per item.
1078 1080 label_attribute_of_issue: Issue's %{name}
1079 1081 permission_add_documents: Add documents
1080 1082 permission_edit_documents: Edit documents
1081 1083 permission_delete_documents: Delete documents
1082 1084 label_gantt_progress_line: Progress line
1083 1085 setting_jsonp_enabled: Enable JSONP support
1084 1086 field_inherit_members: Inherit members
1085 1087 field_closed_on: Closed
1086 1088 field_generate_password: Generate password
1087 1089 setting_default_projects_tracker_ids: Default trackers for new projects
1088 1090 label_total_time: Total
1089 1091 notice_account_not_activated_yet: You haven't activated your account yet. If you want
1090 1092 to receive a new activation email, please <a href="%{url}">click this link</a>.
1091 1093 notice_account_locked: Your account is locked.
1092 1094 label_hidden: Hidden
1093 1095 label_visibility_private: to me only
1094 1096 label_visibility_roles: to these roles only
1095 1097 label_visibility_public: to any users
1096 1098 field_must_change_passwd: Must change password at next logon
1097 1099 notice_new_password_must_be_different: The new password must be different from the
1098 1100 current password
1099 1101 setting_mail_handler_excluded_filenames: Exclude attachments by name
1100 1102 text_convert_available: ImageMagick convert available (optional)
1101 1103 label_link: Link
1102 1104 label_only: only
1103 1105 label_drop_down_list: drop-down list
1104 1106 label_checkboxes: checkboxes
1105 1107 label_link_values_to: Link values to URL
1106 1108 setting_force_default_language_for_anonymous: Force default language for anonymous
1107 1109 users
1108 1110 setting_force_default_language_for_loggedin: Force default language for logged-in
1109 1111 users
1110 1112 label_custom_field_select_type: Select the type of object to which the custom field
1111 1113 is to be attached
1112 1114 label_issue_assigned_to_updated: Assignee updated
1113 1115 label_check_for_updates: Check for updates
1114 1116 label_latest_compatible_version: Latest compatible version
1115 1117 label_unknown_plugin: Unknown plugin
1116 1118 label_radio_buttons: radio buttons
1117 1119 label_group_anonymous: Anonymous users
1118 1120 label_group_non_member: Non member users
1119 1121 label_add_projects: Add projects
1120 1122 field_default_status: Default status
1121 1123 text_subversion_repository_note: 'Examples: file:///, http://, https://, svn://, svn+[tunnelscheme]://'
1122 1124 field_users_visibility: Users visibility
1123 1125 label_users_visibility_all: All active users
1124 1126 label_users_visibility_members_of_visible_projects: Members of visible projects
1125 1127 label_edit_attachments: Edit attached files
1126 1128 setting_link_copied_issue: Link issues on copy
1127 1129 label_link_copied_issue: Link copied issue
1128 1130 label_ask: Ask
1129 1131 label_search_attachments_yes: Search attachment filenames and descriptions
1130 1132 label_search_attachments_no: Do not search attachments
1131 1133 label_search_attachments_only: Search attachments only
1132 1134 label_search_open_issues_only: Open issues only
1133 1135 field_address: Email
1134 1136 setting_max_additional_emails: Maximum number of additional email addresses
1135 1137 label_email_address_plural: Emails
1136 1138 label_email_address_add: Add email address
1137 1139 label_enable_notifications: Enable notifications
1138 1140 label_disable_notifications: Disable notifications
1139 1141 setting_search_results_per_page: Search results per page
1140 1142 label_blank_value: blank
1141 1143 permission_copy_issues: Copy issues
1142 1144 error_password_expired: Your password has expired or the administrator requires you
1143 1145 to change it.
1144 1146 field_time_entries_visibility: Time logs visibility
1145 1147 setting_password_max_age: Require password change after
1146 1148 label_parent_task_attributes: Parent tasks attributes
1147 1149 label_parent_task_attributes_derived: Calculated from subtasks
1148 1150 label_parent_task_attributes_independent: Independent of subtasks
1149 1151 label_time_entries_visibility_all: All time entries
1150 1152 label_time_entries_visibility_own: Time entries created by the user
1151 1153 label_member_management: Member management
1152 1154 label_member_management_all_roles: All roles
1153 1155 label_member_management_selected_roles_only: Only these roles
1154 1156 label_password_required: Confirm your password to continue
1155 1157 label_total_spent_time: Overall spent time
1156 1158 notice_import_finished: All %{count} items have been imported.
1157 1159 notice_import_finished_with_errors: ! '%{count} out of %{total} items could not be
1158 1160 imported.'
1159 1161 error_invalid_file_encoding: The file is not a valid %{encoding} encoded file
1160 1162 error_invalid_csv_file_or_settings: The file is not a CSV file or does not match the
1161 1163 settings below
1162 1164 error_can_not_read_import_file: An error occurred while reading the file to import
1163 1165 permission_import_issues: Import issues
1164 1166 label_import_issues: Import issues
1165 1167 label_select_file_to_import: Select the file to import
1166 1168 label_fields_separator: Field separator
1167 1169 label_fields_wrapper: Field wrapper
1168 1170 label_encoding: Encoding
1169 1171 label_comma_char: Comma
1170 1172 label_semi_colon_char: Semi colon
1171 1173 label_quote_char: Quote
1172 1174 label_double_quote_char: Double quote
1173 1175 label_fields_mapping: Fields mapping
1174 1176 label_file_content_preview: File content preview
1175 1177 label_create_missing_values: Create missing values
1176 1178 button_import: Import
1177 1179 field_total_estimated_hours: Total estimated time
1178 1180 label_api: API
1179 1181 label_total_plural: Totals
1180 1182 label_assigned_issues: Assigned issues
1181 1183 label_field_format_enumeration: Key/value list
1182 1184 label_f_hour_short: '%{value} h'
1183 1185 field_default_version: Default version
1184 1186 error_attachment_extension_not_allowed: Attachment extension %{extension} is not allowed
1185 1187 setting_attachment_extensions_allowed: Allowed extensions
1186 1188 setting_attachment_extensions_denied: Disallowed extensions
@@ -1,1171 +1,1173
1 1 en:
2 2 # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl)
3 3 direction: ltr
4 4 date:
5 5 formats:
6 6 # Use the strftime parameters for formats.
7 7 # When no format has been given, it uses default.
8 8 # You can provide other formats here if you like!
9 9 default: "%m/%d/%Y"
10 10 short: "%b %d"
11 11 long: "%B %d, %Y"
12 12
13 13 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
14 14 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
15 15
16 16 # Don't forget the nil at the beginning; there's no such thing as a 0th month
17 17 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
18 18 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
19 19 # Used in date_select and datime_select.
20 20 order:
21 21 - :year
22 22 - :month
23 23 - :day
24 24
25 25 time:
26 26 formats:
27 27 default: "%m/%d/%Y %I:%M %p"
28 28 time: "%I:%M %p"
29 29 short: "%d %b %H:%M"
30 30 long: "%B %d, %Y %H:%M"
31 31 am: "am"
32 32 pm: "pm"
33 33
34 34 datetime:
35 35 distance_in_words:
36 36 half_a_minute: "half a minute"
37 37 less_than_x_seconds:
38 38 one: "less than 1 second"
39 39 other: "less than %{count} seconds"
40 40 x_seconds:
41 41 one: "1 second"
42 42 other: "%{count} seconds"
43 43 less_than_x_minutes:
44 44 one: "less than a minute"
45 45 other: "less than %{count} minutes"
46 46 x_minutes:
47 47 one: "1 minute"
48 48 other: "%{count} minutes"
49 49 about_x_hours:
50 50 one: "about 1 hour"
51 51 other: "about %{count} hours"
52 52 x_hours:
53 53 one: "1 hour"
54 54 other: "%{count} hours"
55 55 x_days:
56 56 one: "1 day"
57 57 other: "%{count} days"
58 58 about_x_months:
59 59 one: "about 1 month"
60 60 other: "about %{count} months"
61 61 x_months:
62 62 one: "1 month"
63 63 other: "%{count} months"
64 64 about_x_years:
65 65 one: "about 1 year"
66 66 other: "about %{count} years"
67 67 over_x_years:
68 68 one: "over 1 year"
69 69 other: "over %{count} years"
70 70 almost_x_years:
71 71 one: "almost 1 year"
72 72 other: "almost %{count} years"
73 73
74 74 number:
75 75 format:
76 76 separator: "."
77 77 delimiter: ""
78 78 precision: 3
79 79
80 80 human:
81 81 format:
82 82 delimiter: ""
83 83 precision: 3
84 84 storage_units:
85 85 format: "%n %u"
86 86 units:
87 87 byte:
88 88 one: "Byte"
89 89 other: "Bytes"
90 90 kb: "KB"
91 91 mb: "MB"
92 92 gb: "GB"
93 93 tb: "TB"
94 94
95 95 # Used in array.to_sentence.
96 96 support:
97 97 array:
98 98 sentence_connector: "and"
99 99 skip_last_comma: false
100 100
101 101 activerecord:
102 102 errors:
103 103 template:
104 104 header:
105 105 one: "1 error prohibited this %{model} from being saved"
106 106 other: "%{count} errors prohibited this %{model} from being saved"
107 107 messages:
108 108 inclusion: "is not included in the list"
109 109 exclusion: "is reserved"
110 110 invalid: "is invalid"
111 111 confirmation: "doesn't match confirmation"
112 112 accepted: "must be accepted"
113 113 empty: "cannot be empty"
114 114 blank: "cannot be blank"
115 115 too_long: "is too long (maximum is %{count} characters)"
116 116 too_short: "is too short (minimum is %{count} characters)"
117 117 wrong_length: "is the wrong length (should be %{count} characters)"
118 118 taken: "has already been taken"
119 119 not_a_number: "is not a number"
120 120 not_a_date: "is not a valid date"
121 121 greater_than: "must be greater than %{count}"
122 122 greater_than_or_equal_to: "must be greater than or equal to %{count}"
123 123 equal_to: "must be equal to %{count}"
124 124 less_than: "must be less than %{count}"
125 125 less_than_or_equal_to: "must be less than or equal to %{count}"
126 126 odd: "must be odd"
127 127 even: "must be even"
128 128 greater_than_start_date: "must be greater than start date"
129 129 not_same_project: "doesn't belong to the same project"
130 130 circular_dependency: "This relation would create a circular dependency"
131 131 cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks"
132 132 earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues"
133 133
134 134 actionview_instancetag_blank_option: Please select
135 135
136 136 general_text_No: 'No'
137 137 general_text_Yes: 'Yes'
138 138 general_text_no: 'no'
139 139 general_text_yes: 'yes'
140 140 general_lang_name: 'English'
141 141 general_csv_separator: ','
142 142 general_csv_decimal_separator: '.'
143 143 general_csv_encoding: ISO-8859-1
144 144 general_pdf_fontname: freesans
145 145 general_first_day_of_week: '7'
146 146
147 147 notice_account_updated: Account was successfully updated.
148 148 notice_account_invalid_creditentials: Invalid user or password
149 149 notice_account_password_updated: Password was successfully updated.
150 150 notice_account_wrong_password: Wrong password
151 151 notice_account_register_done: Account was successfully created. An email containing the instructions to activate your account was sent to %{email}.
152 152 notice_account_unknown_email: Unknown user.
153 153 notice_account_not_activated_yet: You haven't activated your account yet. If you want to receive a new activation email, please <a href="%{url}">click this link</a>.
154 154 notice_account_locked: Your account is locked.
155 155 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
156 156 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
157 157 notice_account_activated: Your account has been activated. You can now log in.
158 158 notice_successful_create: Successful creation.
159 159 notice_successful_update: Successful update.
160 160 notice_successful_delete: Successful deletion.
161 161 notice_successful_connection: Successful connection.
162 162 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
163 163 notice_locking_conflict: Data has been updated by another user.
164 164 notice_not_authorized: You are not authorized to access this page.
165 165 notice_not_authorized_archived_project: The project you're trying to access has been archived.
166 166 notice_email_sent: "An email was sent to %{value}"
167 167 notice_email_error: "An error occurred while sending mail (%{value})"
168 168 notice_feeds_access_key_reseted: Your Atom access key was reset.
169 169 notice_api_access_key_reseted: Your API access key was reset.
170 170 notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}."
171 171 notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
172 172 notice_failed_to_save_members: "Failed to save member(s): %{errors}."
173 173 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
174 174 notice_account_pending: "Your account was created and is now pending administrator approval."
175 175 notice_default_data_loaded: Default configuration successfully loaded.
176 176 notice_unable_delete_version: Unable to delete version.
177 177 notice_unable_delete_time_entry: Unable to delete time log entry.
178 178 notice_issue_done_ratios_updated: Issue done ratios updated.
179 179 notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
180 180 notice_issue_successful_create: "Issue %{id} created."
181 181 notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it."
182 182 notice_account_deleted: "Your account has been permanently deleted."
183 183 notice_user_successful_create: "User %{id} created."
184 184 notice_new_password_must_be_different: The new password must be different from the current password
185 185 notice_import_finished: "All %{count} items have been imported."
186 186 notice_import_finished_with_errors: "%{count} out of %{total} items could not be imported."
187 187
188 188 error_can_t_load_default_data: "Default configuration could not be loaded: %{value}"
189 189 error_scm_not_found: "The entry or revision was not found in the repository."
190 190 error_scm_command_failed: "An error occurred when trying to access the repository: %{value}"
191 191 error_scm_annotate: "The entry does not exist or cannot be annotated."
192 192 error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size."
193 193 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
194 194 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
195 195 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
196 196 error_can_not_delete_custom_field: Unable to delete custom field
197 197 error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted."
198 198 error_can_not_remove_role: "This role is in use and cannot be deleted."
199 199 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened'
200 200 error_can_not_archive_project: This project cannot be archived
201 201 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
202 202 error_workflow_copy_source: 'Please select a source tracker or role'
203 203 error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
204 204 error_unable_delete_issue_status: 'Unable to delete issue status'
205 205 error_unable_to_connect: "Unable to connect (%{value})"
206 206 error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})"
207 207 error_session_expired: "Your session has expired. Please login again."
208 208 warning_attachments_not_saved: "%{count} file(s) could not be saved."
209 209 error_password_expired: "Your password has expired or the administrator requires you to change it."
210 210 error_invalid_file_encoding: "The file is not a valid %{encoding} encoded file"
211 211 error_invalid_csv_file_or_settings: "The file is not a CSV file or does not match the settings below"
212 212 error_can_not_read_import_file: "An error occurred while reading the file to import"
213 213 error_attachment_extension_not_allowed: "Attachment extension %{extension} is not allowed"
214 214
215 215 mail_subject_lost_password: "Your %{value} password"
216 216 mail_body_lost_password: 'To change your password, click on the following link:'
217 217 mail_subject_register: "Your %{value} account activation"
218 218 mail_body_register: 'To activate your account, click on the following link:'
219 219 mail_body_account_information_external: "You can use your %{value} account to log in."
220 220 mail_body_account_information: Your account information
221 221 mail_subject_account_activation_request: "%{value} account activation request"
222 222 mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:"
223 223 mail_subject_reminder: "%{count} issue(s) due in the next %{days} days"
224 224 mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:"
225 225 mail_subject_wiki_content_added: "'%{id}' wiki page has been added"
226 226 mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}."
227 227 mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated"
228 228 mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}."
229 229
230 230 field_name: Name
231 231 field_description: Description
232 232 field_summary: Summary
233 233 field_is_required: Required
234 234 field_firstname: First name
235 235 field_lastname: Last name
236 236 field_mail: Email
237 237 field_address: Email
238 238 field_filename: File
239 239 field_filesize: Size
240 240 field_downloads: Downloads
241 241 field_author: Author
242 242 field_created_on: Created
243 243 field_updated_on: Updated
244 244 field_closed_on: Closed
245 245 field_field_format: Format
246 246 field_is_for_all: For all projects
247 247 field_possible_values: Possible values
248 248 field_regexp: Regular expression
249 249 field_min_length: Minimum length
250 250 field_max_length: Maximum length
251 251 field_value: Value
252 252 field_category: Category
253 253 field_title: Title
254 254 field_project: Project
255 255 field_issue: Issue
256 256 field_status: Status
257 257 field_notes: Notes
258 258 field_is_closed: Issue closed
259 259 field_is_default: Default value
260 260 field_tracker: Tracker
261 261 field_subject: Subject
262 262 field_due_date: Due date
263 263 field_assigned_to: Assignee
264 264 field_priority: Priority
265 265 field_fixed_version: Target version
266 266 field_user: User
267 267 field_principal: Principal
268 268 field_role: Role
269 269 field_homepage: Homepage
270 270 field_is_public: Public
271 271 field_parent: Subproject of
272 272 field_is_in_roadmap: Issues displayed in roadmap
273 273 field_login: Login
274 274 field_mail_notification: Email notifications
275 275 field_admin: Administrator
276 276 field_last_login_on: Last connection
277 277 field_language: Language
278 278 field_effective_date: Date
279 279 field_password: Password
280 280 field_new_password: New password
281 281 field_password_confirmation: Confirmation
282 282 field_version: Version
283 283 field_type: Type
284 284 field_host: Host
285 285 field_port: Port
286 286 field_account: Account
287 287 field_base_dn: Base DN
288 288 field_attr_login: Login attribute
289 289 field_attr_firstname: Firstname attribute
290 290 field_attr_lastname: Lastname attribute
291 291 field_attr_mail: Email attribute
292 292 field_onthefly: On-the-fly user creation
293 293 field_start_date: Start date
294 294 field_done_ratio: "% Done"
295 295 field_auth_source: Authentication mode
296 296 field_hide_mail: Hide my email address
297 297 field_comments: Comment
298 298 field_url: URL
299 299 field_start_page: Start page
300 300 field_subproject: Subproject
301 301 field_hours: Hours
302 302 field_activity: Activity
303 303 field_spent_on: Date
304 304 field_identifier: Identifier
305 305 field_is_filter: Used as a filter
306 306 field_issue_to: Related issue
307 307 field_delay: Delay
308 308 field_assignable: Issues can be assigned to this role
309 309 field_redirect_existing_links: Redirect existing links
310 310 field_estimated_hours: Estimated time
311 311 field_column_names: Columns
312 312 field_time_entries: Log time
313 313 field_time_zone: Time zone
314 314 field_searchable: Searchable
315 315 field_default_value: Default value
316 316 field_comments_sorting: Display comments
317 317 field_parent_title: Parent page
318 318 field_editable: Editable
319 319 field_watcher: Watcher
320 320 field_identity_url: OpenID URL
321 321 field_content: Content
322 322 field_group_by: Group results by
323 323 field_sharing: Sharing
324 324 field_parent_issue: Parent task
325 325 field_member_of_group: "Assignee's group"
326 326 field_assigned_to_role: "Assignee's role"
327 327 field_text: Text field
328 328 field_visible: Visible
329 329 field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
330 330 field_issues_visibility: Issues visibility
331 331 field_is_private: Private
332 332 field_commit_logs_encoding: Commit messages encoding
333 333 field_scm_path_encoding: Path encoding
334 334 field_path_to_repository: Path to repository
335 335 field_root_directory: Root directory
336 336 field_cvsroot: CVSROOT
337 337 field_cvs_module: Module
338 338 field_repository_is_default: Main repository
339 339 field_multiple: Multiple values
340 340 field_auth_source_ldap_filter: LDAP filter
341 341 field_core_fields: Standard fields
342 342 field_timeout: "Timeout (in seconds)"
343 343 field_board_parent: Parent forum
344 344 field_private_notes: Private notes
345 345 field_inherit_members: Inherit members
346 346 field_generate_password: Generate password
347 347 field_must_change_passwd: Must change password at next logon
348 348 field_default_status: Default status
349 349 field_users_visibility: Users visibility
350 350 field_time_entries_visibility: Time logs visibility
351 351 field_total_estimated_hours: Total estimated time
352 352 field_default_version: Default version
353 353
354 354 setting_app_title: Application title
355 355 setting_app_subtitle: Application subtitle
356 356 setting_welcome_text: Welcome text
357 357 setting_default_language: Default language
358 358 setting_login_required: Authentication required
359 359 setting_self_registration: Self-registration
360 360 setting_attachment_max_size: Maximum attachment size
361 361 setting_issues_export_limit: Issues export limit
362 362 setting_mail_from: Emission email address
363 363 setting_bcc_recipients: Blind carbon copy recipients (bcc)
364 364 setting_plain_text_mail: Plain text mail (no HTML)
365 365 setting_host_name: Host name and path
366 366 setting_text_formatting: Text formatting
367 367 setting_wiki_compression: Wiki history compression
368 368 setting_feeds_limit: Maximum number of items in Atom feeds
369 369 setting_default_projects_public: New projects are public by default
370 370 setting_autofetch_changesets: Fetch commits automatically
371 371 setting_sys_api_enabled: Enable WS for repository management
372 372 setting_commit_ref_keywords: Referencing keywords
373 373 setting_commit_fix_keywords: Fixing keywords
374 374 setting_autologin: Autologin
375 375 setting_date_format: Date format
376 376 setting_time_format: Time format
377 377 setting_cross_project_issue_relations: Allow cross-project issue relations
378 378 setting_cross_project_subtasks: Allow cross-project subtasks
379 379 setting_issue_list_default_columns: Default columns displayed on the issue list
380 380 setting_repositories_encodings: Attachments and repositories encodings
381 381 setting_emails_header: Email header
382 382 setting_emails_footer: Email footer
383 383 setting_protocol: Protocol
384 384 setting_per_page_options: Objects per page options
385 385 setting_user_format: Users display format
386 386 setting_activity_days_default: Days displayed on project activity
387 387 setting_display_subprojects_issues: Display subprojects issues on main projects by default
388 388 setting_enabled_scm: Enabled SCM
389 389 setting_mail_handler_body_delimiters: "Truncate emails after one of these lines"
390 390 setting_mail_handler_api_enabled: Enable WS for incoming emails
391 391 setting_mail_handler_api_key: API key
392 392 setting_sequential_project_identifiers: Generate sequential project identifiers
393 393 setting_gravatar_enabled: Use Gravatar user icons
394 394 setting_gravatar_default: Default Gravatar image
395 395 setting_diff_max_lines_displayed: Maximum number of diff lines displayed
396 396 setting_file_max_size_displayed: Maximum size of text files displayed inline
397 397 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
398 398 setting_openid: Allow OpenID login and registration
399 399 setting_password_max_age: Require password change after
400 400 setting_password_min_length: Minimum password length
401 401 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
402 402 setting_default_projects_modules: Default enabled modules for new projects
403 403 setting_issue_done_ratio: Calculate the issue done ratio with
404 404 setting_issue_done_ratio_issue_field: Use the issue field
405 405 setting_issue_done_ratio_issue_status: Use the issue status
406 406 setting_start_of_week: Start calendars on
407 407 setting_rest_api_enabled: Enable REST web service
408 408 setting_cache_formatted_text: Cache formatted text
409 409 setting_default_notification_option: Default notification option
410 410 setting_commit_logtime_enabled: Enable time logging
411 411 setting_commit_logtime_activity_id: Activity for logged time
412 412 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
413 413 setting_issue_group_assignment: Allow issue assignment to groups
414 414 setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
415 415 setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed
416 416 setting_unsubscribe: Allow users to delete their own account
417 417 setting_session_lifetime: Session maximum lifetime
418 418 setting_session_timeout: Session inactivity timeout
419 419 setting_thumbnails_enabled: Display attachment thumbnails
420 420 setting_thumbnails_size: Thumbnails size (in pixels)
421 421 setting_non_working_week_days: Non-working days
422 422 setting_jsonp_enabled: Enable JSONP support
423 423 setting_default_projects_tracker_ids: Default trackers for new projects
424 424 setting_mail_handler_excluded_filenames: Exclude attachments by name
425 425 setting_force_default_language_for_anonymous: Force default language for anonymous users
426 426 setting_force_default_language_for_loggedin: Force default language for logged-in users
427 427 setting_link_copied_issue: Link issues on copy
428 428 setting_max_additional_emails: Maximum number of additional email addresses
429 429 setting_search_results_per_page: Search results per page
430 430 setting_attachment_extensions_allowed: Allowed extensions
431 431 setting_attachment_extensions_denied: Disallowed extensions
432 432
433 433 permission_add_project: Create project
434 434 permission_add_subprojects: Create subprojects
435 435 permission_edit_project: Edit project
436 436 permission_close_project: Close / reopen the project
437 437 permission_select_project_modules: Select project modules
438 438 permission_manage_members: Manage members
439 439 permission_manage_project_activities: Manage project activities
440 440 permission_manage_versions: Manage versions
441 441 permission_manage_categories: Manage issue categories
442 442 permission_view_issues: View Issues
443 443 permission_add_issues: Add issues
444 444 permission_edit_issues: Edit issues
445 445 permission_copy_issues: Copy issues
446 446 permission_manage_issue_relations: Manage issue relations
447 447 permission_set_issues_private: Set issues public or private
448 448 permission_set_own_issues_private: Set own issues public or private
449 449 permission_add_issue_notes: Add notes
450 450 permission_edit_issue_notes: Edit notes
451 451 permission_edit_own_issue_notes: Edit own notes
452 452 permission_view_private_notes: View private notes
453 453 permission_set_notes_private: Set notes as private
454 454 permission_move_issues: Move issues
455 455 permission_delete_issues: Delete issues
456 456 permission_manage_public_queries: Manage public queries
457 457 permission_save_queries: Save queries
458 458 permission_view_gantt: View gantt chart
459 459 permission_view_calendar: View calendar
460 460 permission_view_issue_watchers: View watchers list
461 461 permission_add_issue_watchers: Add watchers
462 462 permission_delete_issue_watchers: Delete watchers
463 463 permission_log_time: Log spent time
464 464 permission_view_time_entries: View spent time
465 465 permission_edit_time_entries: Edit time logs
466 466 permission_edit_own_time_entries: Edit own time logs
467 467 permission_manage_news: Manage news
468 468 permission_comment_news: Comment news
469 469 permission_view_documents: View documents
470 470 permission_add_documents: Add documents
471 471 permission_edit_documents: Edit documents
472 472 permission_delete_documents: Delete documents
473 473 permission_manage_files: Manage files
474 474 permission_view_files: View files
475 475 permission_manage_wiki: Manage wiki
476 476 permission_rename_wiki_pages: Rename wiki pages
477 477 permission_delete_wiki_pages: Delete wiki pages
478 478 permission_view_wiki_pages: View wiki
479 479 permission_view_wiki_edits: View wiki history
480 480 permission_edit_wiki_pages: Edit wiki pages
481 481 permission_delete_wiki_pages_attachments: Delete attachments
482 482 permission_protect_wiki_pages: Protect wiki pages
483 483 permission_manage_repository: Manage repository
484 484 permission_browse_repository: Browse repository
485 485 permission_view_changesets: View changesets
486 486 permission_commit_access: Commit access
487 487 permission_manage_boards: Manage forums
488 488 permission_view_messages: View messages
489 489 permission_add_messages: Post messages
490 490 permission_edit_messages: Edit messages
491 491 permission_edit_own_messages: Edit own messages
492 492 permission_delete_messages: Delete messages
493 493 permission_delete_own_messages: Delete own messages
494 494 permission_export_wiki_pages: Export wiki pages
495 495 permission_manage_subtasks: Manage subtasks
496 496 permission_manage_related_issues: Manage related issues
497 497 permission_import_issues: Import issues
498 498
499 499 project_module_issue_tracking: Issue tracking
500 500 project_module_time_tracking: Time tracking
501 501 project_module_news: News
502 502 project_module_documents: Documents
503 503 project_module_files: Files
504 504 project_module_wiki: Wiki
505 505 project_module_repository: Repository
506 506 project_module_boards: Forums
507 507 project_module_calendar: Calendar
508 508 project_module_gantt: Gantt
509 509
510 510 label_user: User
511 511 label_user_plural: Users
512 512 label_user_new: New user
513 513 label_user_anonymous: Anonymous
514 514 label_project: Project
515 515 label_project_new: New project
516 516 label_project_plural: Projects
517 517 label_x_projects:
518 518 zero: no projects
519 519 one: 1 project
520 520 other: "%{count} projects"
521 521 label_project_all: All Projects
522 522 label_project_latest: Latest projects
523 523 label_issue: Issue
524 524 label_issue_new: New issue
525 525 label_issue_plural: Issues
526 526 label_issue_view_all: View all issues
527 527 label_issues_by: "Issues by %{value}"
528 528 label_issue_added: Issue added
529 529 label_issue_updated: Issue updated
530 530 label_issue_note_added: Note added
531 531 label_issue_status_updated: Status updated
532 532 label_issue_assigned_to_updated: Assignee updated
533 533 label_issue_priority_updated: Priority updated
534 534 label_document: Document
535 535 label_document_new: New document
536 536 label_document_plural: Documents
537 537 label_document_added: Document added
538 538 label_role: Role
539 539 label_role_plural: Roles
540 540 label_role_new: New role
541 541 label_role_and_permissions: Roles and permissions
542 542 label_role_anonymous: Anonymous
543 543 label_role_non_member: Non member
544 544 label_member: Member
545 545 label_member_new: New member
546 546 label_member_plural: Members
547 547 label_tracker: Tracker
548 548 label_tracker_plural: Trackers
549 549 label_tracker_new: New tracker
550 550 label_workflow: Workflow
551 551 label_issue_status: Issue status
552 552 label_issue_status_plural: Issue statuses
553 553 label_issue_status_new: New status
554 554 label_issue_category: Issue category
555 555 label_issue_category_plural: Issue categories
556 556 label_issue_category_new: New category
557 557 label_custom_field: Custom field
558 558 label_custom_field_plural: Custom fields
559 559 label_custom_field_new: New custom field
560 560 label_enumerations: Enumerations
561 561 label_enumeration_new: New value
562 562 label_information: Information
563 563 label_information_plural: Information
564 564 label_please_login: Please log in
565 565 label_register: Register
566 566 label_login_with_open_id_option: or login with OpenID
567 567 label_password_lost: Lost password
568 568 label_password_required: Confirm your password to continue
569 569 label_home: Home
570 570 label_my_page: My page
571 571 label_my_account: My account
572 572 label_my_projects: My projects
573 573 label_my_page_block: My page block
574 574 label_administration: Administration
575 575 label_login: Sign in
576 576 label_logout: Sign out
577 577 label_help: Help
578 578 label_reported_issues: Reported issues
579 579 label_assigned_issues: Assigned issues
580 580 label_assigned_to_me_issues: Issues assigned to me
581 581 label_last_login: Last connection
582 582 label_registered_on: Registered on
583 583 label_activity: Activity
584 584 label_overall_activity: Overall activity
585 585 label_user_activity: "%{value}'s activity"
586 586 label_new: New
587 587 label_logged_as: Logged in as
588 588 label_environment: Environment
589 589 label_authentication: Authentication
590 590 label_auth_source: Authentication mode
591 591 label_auth_source_new: New authentication mode
592 592 label_auth_source_plural: Authentication modes
593 593 label_subproject_plural: Subprojects
594 594 label_subproject_new: New subproject
595 595 label_and_its_subprojects: "%{value} and its subprojects"
596 596 label_min_max_length: Min - Max length
597 597 label_list: List
598 598 label_date: Date
599 599 label_integer: Integer
600 600 label_float: Float
601 601 label_boolean: Boolean
602 602 label_string: Text
603 603 label_text: Long text
604 604 label_attribute: Attribute
605 605 label_attribute_plural: Attributes
606 606 label_no_data: No data to display
607 607 label_change_status: Change status
608 608 label_history: History
609 609 label_attachment: File
610 610 label_attachment_new: New file
611 611 label_attachment_delete: Delete file
612 612 label_attachment_plural: Files
613 613 label_file_added: File added
614 614 label_report: Report
615 615 label_report_plural: Reports
616 616 label_news: News
617 617 label_news_new: Add news
618 618 label_news_plural: News
619 619 label_news_latest: Latest news
620 620 label_news_view_all: View all news
621 621 label_news_added: News added
622 622 label_news_comment_added: Comment added to a news
623 623 label_settings: Settings
624 624 label_overview: Overview
625 625 label_version: Version
626 626 label_version_new: New version
627 627 label_version_plural: Versions
628 628 label_close_versions: Close completed versions
629 629 label_confirmation: Confirmation
630 630 label_export_to: 'Also available in:'
631 631 label_read: Read...
632 632 label_public_projects: Public projects
633 633 label_open_issues: open
634 634 label_open_issues_plural: open
635 635 label_closed_issues: closed
636 636 label_closed_issues_plural: closed
637 637 label_x_open_issues_abbr:
638 638 zero: 0 open
639 639 one: 1 open
640 640 other: "%{count} open"
641 641 label_x_closed_issues_abbr:
642 642 zero: 0 closed
643 643 one: 1 closed
644 644 other: "%{count} closed"
645 645 label_x_issues:
646 646 zero: 0 issues
647 647 one: 1 issue
648 648 other: "%{count} issues"
649 649 label_total: Total
650 650 label_total_plural: Totals
651 651 label_total_time: Total time
652 652 label_permissions: Permissions
653 653 label_current_status: Current status
654 654 label_new_statuses_allowed: New statuses allowed
655 655 label_all: all
656 656 label_any: any
657 657 label_none: none
658 658 label_nobody: nobody
659 659 label_next: Next
660 660 label_previous: Previous
661 661 label_used_by: Used by
662 662 label_details: Details
663 663 label_add_note: Add a note
664 664 label_calendar: Calendar
665 665 label_months_from: months from
666 666 label_gantt: Gantt
667 667 label_internal: Internal
668 668 label_last_changes: "last %{count} changes"
669 669 label_change_view_all: View all changes
670 670 label_personalize_page: Personalize this page
671 671 label_comment: Comment
672 672 label_comment_plural: Comments
673 673 label_x_comments:
674 674 zero: no comments
675 675 one: 1 comment
676 676 other: "%{count} comments"
677 677 label_comment_add: Add a comment
678 678 label_comment_added: Comment added
679 679 label_comment_delete: Delete comments
680 680 label_query: Custom query
681 681 label_query_plural: Custom queries
682 682 label_query_new: New query
683 683 label_my_queries: My custom queries
684 684 label_filter_add: Add filter
685 685 label_filter_plural: Filters
686 686 label_equals: is
687 687 label_not_equals: is not
688 688 label_in_less_than: in less than
689 689 label_in_more_than: in more than
690 690 label_in_the_next_days: in the next
691 691 label_in_the_past_days: in the past
692 692 label_greater_or_equal: '>='
693 693 label_less_or_equal: '<='
694 694 label_between: between
695 695 label_in: in
696 696 label_today: today
697 697 label_all_time: all time
698 698 label_yesterday: yesterday
699 699 label_this_week: this week
700 700 label_last_week: last week
701 701 label_last_n_weeks: "last %{count} weeks"
702 702 label_last_n_days: "last %{count} days"
703 703 label_this_month: this month
704 704 label_last_month: last month
705 705 label_this_year: this year
706 706 label_date_range: Date range
707 707 label_less_than_ago: less than days ago
708 708 label_more_than_ago: more than days ago
709 709 label_ago: days ago
710 710 label_contains: contains
711 711 label_not_contains: doesn't contain
712 712 label_any_issues_in_project: any issues in project
713 713 label_any_issues_not_in_project: any issues not in project
714 714 label_no_issues_in_project: no issues in project
715 label_any_open_issues: any open issues
716 label_no_open_issues: no open issues
715 717 label_day_plural: days
716 718 label_repository: Repository
717 719 label_repository_new: New repository
718 720 label_repository_plural: Repositories
719 721 label_browse: Browse
720 722 label_branch: Branch
721 723 label_tag: Tag
722 724 label_revision: Revision
723 725 label_revision_plural: Revisions
724 726 label_revision_id: "Revision %{value}"
725 727 label_associated_revisions: Associated revisions
726 728 label_added: added
727 729 label_modified: modified
728 730 label_copied: copied
729 731 label_renamed: renamed
730 732 label_deleted: deleted
731 733 label_latest_revision: Latest revision
732 734 label_latest_revision_plural: Latest revisions
733 735 label_view_revisions: View revisions
734 736 label_view_all_revisions: View all revisions
735 737 label_max_size: Maximum size
736 738 label_sort_highest: Move to top
737 739 label_sort_higher: Move up
738 740 label_sort_lower: Move down
739 741 label_sort_lowest: Move to bottom
740 742 label_roadmap: Roadmap
741 743 label_roadmap_due_in: "Due in %{value}"
742 744 label_roadmap_overdue: "%{value} late"
743 745 label_roadmap_no_issues: No issues for this version
744 746 label_search: Search
745 747 label_result_plural: Results
746 748 label_all_words: All words
747 749 label_wiki: Wiki
748 750 label_wiki_edit: Wiki edit
749 751 label_wiki_edit_plural: Wiki edits
750 752 label_wiki_page: Wiki page
751 753 label_wiki_page_plural: Wiki pages
752 754 label_index_by_title: Index by title
753 755 label_index_by_date: Index by date
754 756 label_current_version: Current version
755 757 label_preview: Preview
756 758 label_feed_plural: Feeds
757 759 label_changes_details: Details of all changes
758 760 label_issue_tracking: Issue tracking
759 761 label_spent_time: Spent time
760 762 label_total_spent_time: Total spent time
761 763 label_overall_spent_time: Overall spent time
762 764 label_f_hour: "%{value} hour"
763 765 label_f_hour_plural: "%{value} hours"
764 766 label_f_hour_short: "%{value} h"
765 767 label_time_tracking: Time tracking
766 768 label_change_plural: Changes
767 769 label_statistics: Statistics
768 770 label_commits_per_month: Commits per month
769 771 label_commits_per_author: Commits per author
770 772 label_diff: diff
771 773 label_view_diff: View differences
772 774 label_diff_inline: inline
773 775 label_diff_side_by_side: side by side
774 776 label_options: Options
775 777 label_copy_workflow_from: Copy workflow from
776 778 label_permissions_report: Permissions report
777 779 label_watched_issues: Watched issues
778 780 label_related_issues: Related issues
779 781 label_applied_status: Applied status
780 782 label_loading: Loading...
781 783 label_relation_new: New relation
782 784 label_relation_delete: Delete relation
783 785 label_relates_to: Related to
784 786 label_duplicates: Duplicates
785 787 label_duplicated_by: Duplicated by
786 788 label_blocks: Blocks
787 789 label_blocked_by: Blocked by
788 790 label_precedes: Precedes
789 791 label_follows: Follows
790 792 label_copied_to: Copied to
791 793 label_copied_from: Copied from
792 794 label_end_to_start: end to start
793 795 label_end_to_end: end to end
794 796 label_start_to_start: start to start
795 797 label_start_to_end: start to end
796 798 label_stay_logged_in: Stay logged in
797 799 label_disabled: disabled
798 800 label_show_completed_versions: Show completed versions
799 801 label_me: me
800 802 label_board: Forum
801 803 label_board_new: New forum
802 804 label_board_plural: Forums
803 805 label_board_locked: Locked
804 806 label_board_sticky: Sticky
805 807 label_topic_plural: Topics
806 808 label_message_plural: Messages
807 809 label_message_last: Last message
808 810 label_message_new: New message
809 811 label_message_posted: Message added
810 812 label_reply_plural: Replies
811 813 label_send_information: Send account information to the user
812 814 label_year: Year
813 815 label_month: Month
814 816 label_week: Week
815 817 label_date_from: From
816 818 label_date_to: To
817 819 label_language_based: Based on user's language
818 820 label_sort_by: "Sort by %{value}"
819 821 label_send_test_email: Send a test email
820 822 label_feeds_access_key: Atom access key
821 823 label_missing_feeds_access_key: Missing a Atom access key
822 824 label_feeds_access_key_created_on: "Atom access key created %{value} ago"
823 825 label_module_plural: Modules
824 826 label_added_time_by: "Added by %{author} %{age} ago"
825 827 label_updated_time_by: "Updated by %{author} %{age} ago"
826 828 label_updated_time: "Updated %{value} ago"
827 829 label_jump_to_a_project: Jump to a project...
828 830 label_file_plural: Files
829 831 label_changeset_plural: Changesets
830 832 label_default_columns: Default columns
831 833 label_no_change_option: (No change)
832 834 label_bulk_edit_selected_issues: Bulk edit selected issues
833 835 label_bulk_edit_selected_time_entries: Bulk edit selected time entries
834 836 label_theme: Theme
835 837 label_default: Default
836 838 label_search_titles_only: Search titles only
837 839 label_user_mail_option_all: "For any event on all my projects"
838 840 label_user_mail_option_selected: "For any event on the selected projects only..."
839 841 label_user_mail_option_none: "No events"
840 842 label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in"
841 843 label_user_mail_option_only_assigned: "Only for things I am assigned to"
842 844 label_user_mail_option_only_owner: "Only for things I am the owner of"
843 845 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
844 846 label_registration_activation_by_email: account activation by email
845 847 label_registration_manual_activation: manual account activation
846 848 label_registration_automatic_activation: automatic account activation
847 849 label_display_per_page: "Per page: %{value}"
848 850 label_age: Age
849 851 label_change_properties: Change properties
850 852 label_general: General
851 853 label_more: More
852 854 label_scm: SCM
853 855 label_plugins: Plugins
854 856 label_ldap_authentication: LDAP authentication
855 857 label_downloads_abbr: D/L
856 858 label_optional_description: Optional description
857 859 label_add_another_file: Add another file
858 860 label_preferences: Preferences
859 861 label_chronological_order: In chronological order
860 862 label_reverse_chronological_order: In reverse chronological order
861 863 label_planning: Planning
862 864 label_incoming_emails: Incoming emails
863 865 label_generate_key: Generate a key
864 866 label_issue_watchers: Watchers
865 867 label_example: Example
866 868 label_display: Display
867 869 label_sort: Sort
868 870 label_ascending: Ascending
869 871 label_descending: Descending
870 872 label_date_from_to: From %{start} to %{end}
871 873 label_wiki_content_added: Wiki page added
872 874 label_wiki_content_updated: Wiki page updated
873 875 label_group: Group
874 876 label_group_plural: Groups
875 877 label_group_new: New group
876 878 label_group_anonymous: Anonymous users
877 879 label_group_non_member: Non member users
878 880 label_time_entry_plural: Spent time
879 881 label_version_sharing_none: Not shared
880 882 label_version_sharing_descendants: With subprojects
881 883 label_version_sharing_hierarchy: With project hierarchy
882 884 label_version_sharing_tree: With project tree
883 885 label_version_sharing_system: With all projects
884 886 label_update_issue_done_ratios: Update issue done ratios
885 887 label_copy_source: Source
886 888 label_copy_target: Target
887 889 label_copy_same_as_target: Same as target
888 890 label_display_used_statuses_only: Only display statuses that are used by this tracker
889 891 label_api_access_key: API access key
890 892 label_missing_api_access_key: Missing an API access key
891 893 label_api_access_key_created_on: "API access key created %{value} ago"
892 894 label_profile: Profile
893 895 label_subtask_plural: Subtasks
894 896 label_project_copy_notifications: Send email notifications during the project copy
895 897 label_principal_search: "Search for user or group:"
896 898 label_user_search: "Search for user:"
897 899 label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
898 900 label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
899 901 label_issues_visibility_all: All issues
900 902 label_issues_visibility_public: All non private issues
901 903 label_issues_visibility_own: Issues created by or assigned to the user
902 904 label_git_report_last_commit: Report last commit for files and directories
903 905 label_parent_revision: Parent
904 906 label_child_revision: Child
905 907 label_export_options: "%{export_format} export options"
906 908 label_copy_attachments: Copy attachments
907 909 label_copy_subtasks: Copy subtasks
908 910 label_item_position: "%{position} of %{count}"
909 911 label_completed_versions: Completed versions
910 912 label_search_for_watchers: Search for watchers to add
911 913 label_session_expiration: Session expiration
912 914 label_show_closed_projects: View closed projects
913 915 label_status_transitions: Status transitions
914 916 label_fields_permissions: Fields permissions
915 917 label_readonly: Read-only
916 918 label_required: Required
917 919 label_hidden: Hidden
918 920 label_attribute_of_project: "Project's %{name}"
919 921 label_attribute_of_issue: "Issue's %{name}"
920 922 label_attribute_of_author: "Author's %{name}"
921 923 label_attribute_of_assigned_to: "Assignee's %{name}"
922 924 label_attribute_of_user: "User's %{name}"
923 925 label_attribute_of_fixed_version: "Target version's %{name}"
924 926 label_cross_project_descendants: With subprojects
925 927 label_cross_project_tree: With project tree
926 928 label_cross_project_hierarchy: With project hierarchy
927 929 label_cross_project_system: With all projects
928 930 label_gantt_progress_line: Progress line
929 931 label_visibility_private: to me only
930 932 label_visibility_roles: to these roles only
931 933 label_visibility_public: to any users
932 934 label_link: Link
933 935 label_only: only
934 936 label_drop_down_list: drop-down list
935 937 label_checkboxes: checkboxes
936 938 label_radio_buttons: radio buttons
937 939 label_link_values_to: Link values to URL
938 940 label_custom_field_select_type: Select the type of object to which the custom field is to be attached
939 941 label_check_for_updates: Check for updates
940 942 label_latest_compatible_version: Latest compatible version
941 943 label_unknown_plugin: Unknown plugin
942 944 label_add_projects: Add projects
943 945 label_users_visibility_all: All active users
944 946 label_users_visibility_members_of_visible_projects: Members of visible projects
945 947 label_edit_attachments: Edit attached files
946 948 label_link_copied_issue: Link copied issue
947 949 label_ask: Ask
948 950 label_search_attachments_yes: Search attachment filenames and descriptions
949 951 label_search_attachments_no: Do not search attachments
950 952 label_search_attachments_only: Search attachments only
951 953 label_search_open_issues_only: Open issues only
952 954 label_email_address_plural: Emails
953 955 label_email_address_add: Add email address
954 956 label_enable_notifications: Enable notifications
955 957 label_disable_notifications: Disable notifications
956 958 label_blank_value: blank
957 959 label_parent_task_attributes: Parent tasks attributes
958 960 label_parent_task_attributes_derived: Calculated from subtasks
959 961 label_parent_task_attributes_independent: Independent of subtasks
960 962 label_time_entries_visibility_all: All time entries
961 963 label_time_entries_visibility_own: Time entries created by the user
962 964 label_member_management: Member management
963 965 label_member_management_all_roles: All roles
964 966 label_member_management_selected_roles_only: Only these roles
965 967 label_import_issues: Import issues
966 968 label_select_file_to_import: Select the file to import
967 969 label_fields_separator: Field separator
968 970 label_fields_wrapper: Field wrapper
969 971 label_encoding: Encoding
970 972 label_comma_char: Comma
971 973 label_semi_colon_char: Semi colon
972 974 label_quote_char: Quote
973 975 label_double_quote_char: Double quote
974 976 label_fields_mapping: Fields mapping
975 977 label_file_content_preview: File content preview
976 978 label_create_missing_values: Create missing values
977 979 label_api: API
978 980 label_field_format_enumeration: Key/value list
979 981
980 982 button_login: Login
981 983 button_submit: Submit
982 984 button_save: Save
983 985 button_check_all: Check all
984 986 button_uncheck_all: Uncheck all
985 987 button_collapse_all: Collapse all
986 988 button_expand_all: Expand all
987 989 button_delete: Delete
988 990 button_create: Create
989 991 button_create_and_continue: Create and continue
990 992 button_test: Test
991 993 button_edit: Edit
992 994 button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
993 995 button_add: Add
994 996 button_change: Change
995 997 button_apply: Apply
996 998 button_clear: Clear
997 999 button_lock: Lock
998 1000 button_unlock: Unlock
999 1001 button_download: Download
1000 1002 button_list: List
1001 1003 button_view: View
1002 1004 button_move: Move
1003 1005 button_move_and_follow: Move and follow
1004 1006 button_back: Back
1005 1007 button_cancel: Cancel
1006 1008 button_activate: Activate
1007 1009 button_sort: Sort
1008 1010 button_log_time: Log time
1009 1011 button_rollback: Rollback to this version
1010 1012 button_watch: Watch
1011 1013 button_unwatch: Unwatch
1012 1014 button_reply: Reply
1013 1015 button_archive: Archive
1014 1016 button_unarchive: Unarchive
1015 1017 button_reset: Reset
1016 1018 button_rename: Rename
1017 1019 button_change_password: Change password
1018 1020 button_copy: Copy
1019 1021 button_copy_and_follow: Copy and follow
1020 1022 button_annotate: Annotate
1021 1023 button_update: Update
1022 1024 button_configure: Configure
1023 1025 button_quote: Quote
1024 1026 button_duplicate: Duplicate
1025 1027 button_show: Show
1026 1028 button_hide: Hide
1027 1029 button_edit_section: Edit this section
1028 1030 button_export: Export
1029 1031 button_delete_my_account: Delete my account
1030 1032 button_close: Close
1031 1033 button_reopen: Reopen
1032 1034 button_import: Import
1033 1035
1034 1036 status_active: active
1035 1037 status_registered: registered
1036 1038 status_locked: locked
1037 1039
1038 1040 project_status_active: active
1039 1041 project_status_closed: closed
1040 1042 project_status_archived: archived
1041 1043
1042 1044 version_status_open: open
1043 1045 version_status_locked: locked
1044 1046 version_status_closed: closed
1045 1047
1046 1048 field_active: Active
1047 1049
1048 1050 text_select_mail_notifications: Select actions for which email notifications should be sent.
1049 1051 text_regexp_info: eg. ^[A-Z0-9]+$
1050 1052 text_min_max_length_info: 0 means no restriction
1051 1053 text_project_destroy_confirmation: Are you sure you want to delete this project and related data?
1052 1054 text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
1053 1055 text_workflow_edit: Select a role and a tracker to edit the workflow
1054 1056 text_are_you_sure: Are you sure?
1055 1057 text_journal_changed: "%{label} changed from %{old} to %{new}"
1056 1058 text_journal_changed_no_detail: "%{label} updated"
1057 1059 text_journal_set_to: "%{label} set to %{value}"
1058 1060 text_journal_deleted: "%{label} deleted (%{old})"
1059 1061 text_journal_added: "%{label} %{value} added"
1060 1062 text_tip_issue_begin_day: issue beginning this day
1061 1063 text_tip_issue_end_day: issue ending this day
1062 1064 text_tip_issue_begin_end_day: issue beginning and ending this day
1063 1065 text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed, must start with a lower case letter.<br />Once saved, the identifier cannot be changed.'
1064 1066 text_caracters_maximum: "%{count} characters maximum."
1065 1067 text_caracters_minimum: "Must be at least %{count} characters long."
1066 1068 text_length_between: "Length between %{min} and %{max} characters."
1067 1069 text_tracker_no_workflow: No workflow defined for this tracker
1068 1070 text_unallowed_characters: Unallowed characters
1069 1071 text_comma_separated: Multiple values allowed (comma separated).
1070 1072 text_line_separated: Multiple values allowed (one line for each value).
1071 1073 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
1072 1074 text_issue_added: "Issue %{id} has been reported by %{author}."
1073 1075 text_issue_updated: "Issue %{id} has been updated by %{author}."
1074 1076 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content?
1075 1077 text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?"
1076 1078 text_issue_category_destroy_assignments: Remove category assignments
1077 1079 text_issue_category_reassign_to: Reassign issues to this category
1078 1080 text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)."
1079 1081 text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded."
1080 1082 text_load_default_configuration: Load the default configuration
1081 1083 text_status_changed_by_changeset: "Applied in changeset %{value}."
1082 1084 text_time_logged_by_changeset: "Applied in changeset %{value}."
1083 1085 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
1084 1086 text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)."
1085 1087 text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?'
1086 1088 text_select_project_modules: 'Select modules to enable for this project:'
1087 1089 text_default_administrator_account_changed: Default administrator account changed
1088 1090 text_file_repository_writable: Attachments directory writable
1089 1091 text_plugin_assets_writable: Plugin assets directory writable
1090 1092 text_rmagick_available: RMagick available (optional)
1091 1093 text_convert_available: ImageMagick convert available (optional)
1092 1094 text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?"
1093 1095 text_destroy_time_entries: Delete reported hours
1094 1096 text_assign_time_entries_to_project: Assign reported hours to the project
1095 1097 text_reassign_time_entries: 'Reassign reported hours to this issue:'
1096 1098 text_user_wrote: "%{value} wrote:"
1097 1099 text_enumeration_destroy_question: "%{count} objects are assigned to the value β€œ%{name}”."
1098 1100 text_enumeration_category_reassign_to: 'Reassign them to this value:'
1099 1101 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them."
1100 1102 text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
1101 1103 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
1102 1104 text_custom_field_possible_values_info: 'One line for each value'
1103 1105 text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?"
1104 1106 text_wiki_page_nullify_children: "Keep child pages as root pages"
1105 1107 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
1106 1108 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
1107 1109 text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?"
1108 1110 text_zoom_in: Zoom in
1109 1111 text_zoom_out: Zoom out
1110 1112 text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page."
1111 1113 text_scm_path_encoding_note: "Default: UTF-8"
1112 1114 text_subversion_repository_note: "Examples: file:///, http://, https://, svn://, svn+[tunnelscheme]://"
1113 1115 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
1114 1116 text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo)
1115 1117 text_scm_command: Command
1116 1118 text_scm_command_version: Version
1117 1119 text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it.
1118 1120 text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel.
1119 1121 text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)"
1120 1122 text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes"
1121 1123 text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}"
1122 1124 text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it."
1123 1125 text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours."
1124 1126 text_project_closed: This project is closed and read-only.
1125 1127 text_turning_multiple_off: "If you disable multiple values, multiple values will be removed in order to preserve only one value per item."
1126 1128
1127 1129 default_role_manager: Manager
1128 1130 default_role_developer: Developer
1129 1131 default_role_reporter: Reporter
1130 1132 default_tracker_bug: Bug
1131 1133 default_tracker_feature: Feature
1132 1134 default_tracker_support: Support
1133 1135 default_issue_status_new: New
1134 1136 default_issue_status_in_progress: In Progress
1135 1137 default_issue_status_resolved: Resolved
1136 1138 default_issue_status_feedback: Feedback
1137 1139 default_issue_status_closed: Closed
1138 1140 default_issue_status_rejected: Rejected
1139 1141 default_doc_category_user: User documentation
1140 1142 default_doc_category_tech: Technical documentation
1141 1143 default_priority_low: Low
1142 1144 default_priority_normal: Normal
1143 1145 default_priority_high: High
1144 1146 default_priority_urgent: Urgent
1145 1147 default_priority_immediate: Immediate
1146 1148 default_activity_design: Design
1147 1149 default_activity_development: Development
1148 1150
1149 1151 enumeration_issue_priorities: Issue priorities
1150 1152 enumeration_doc_categories: Document categories
1151 1153 enumeration_activities: Activities (time tracking)
1152 1154 enumeration_system_activity: System Activity
1153 1155 description_filter: Filter
1154 1156 description_search: Searchfield
1155 1157 description_choose_project: Projects
1156 1158 description_project_scope: Search scope
1157 1159 description_notes: Notes
1158 1160 description_message_content: Message content
1159 1161 description_query_sort_criteria_attribute: Sort attribute
1160 1162 description_query_sort_criteria_direction: Sort direction
1161 1163 description_user_mail_notification: Mail notification settings
1162 1164 description_available_columns: Available Columns
1163 1165 description_selected_columns: Selected Columns
1164 1166 description_all_columns: All Columns
1165 1167 description_issue_category_reassign: Choose issue category
1166 1168 description_wiki_subpages_reassign: Choose new parent page
1167 1169 description_date_range_list: Choose range from list
1168 1170 description_date_range_interval: Choose range by selecting start and end date
1169 1171 description_date_from: Enter start date
1170 1172 description_date_to: Enter end date
1171 1173 text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
@@ -1,1191 +1,1193
1 1 # French translations for Ruby on Rails
2 2 # by Christian Lescuyer (christian@flyingcoders.com)
3 3 # contributor: Sebastien Grosjean - ZenCocoon.com
4 4 # contributor: Thibaut Cuvelier - Developpez.com
5 5
6 6 fr:
7 7 direction: ltr
8 8 date:
9 9 formats:
10 10 default: "%d/%m/%Y"
11 11 short: "%e %b"
12 12 long: "%e %B %Y"
13 13 long_ordinal: "%e %B %Y"
14 14 only_day: "%e"
15 15
16 16 day_names: [dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi]
17 17 abbr_day_names: [dim, lun, mar, mer, jeu, ven, sam]
18 18
19 19 # Don't forget the nil at the beginning; there's no such thing as a 0th month
20 20 month_names: [~, janvier, fΓ©vrier, mars, avril, mai, juin, juillet, aoΓ»t, septembre, octobre, novembre, dΓ©cembre]
21 21 abbr_month_names: [~, jan., fΓ©v., mar., avr., mai, juin, juil., aoΓ»t, sept., oct., nov., dΓ©c.]
22 22 # Used in date_select and datime_select.
23 23 order:
24 24 - :day
25 25 - :month
26 26 - :year
27 27
28 28 time:
29 29 formats:
30 30 default: "%d/%m/%Y %H:%M"
31 31 time: "%H:%M"
32 32 short: "%d %b %H:%M"
33 33 long: "%A %d %B %Y %H:%M:%S %Z"
34 34 long_ordinal: "%A %d %B %Y %H:%M:%S %Z"
35 35 only_second: "%S"
36 36 am: 'am'
37 37 pm: 'pm'
38 38
39 39 datetime:
40 40 distance_in_words:
41 41 half_a_minute: "30 secondes"
42 42 less_than_x_seconds:
43 43 zero: "moins d'une seconde"
44 44 one: "moins d'uneΒ seconde"
45 45 other: "moins de %{count}Β secondes"
46 46 x_seconds:
47 47 one: "1Β seconde"
48 48 other: "%{count}Β secondes"
49 49 less_than_x_minutes:
50 50 zero: "moins d'une minute"
51 51 one: "moins d'uneΒ minute"
52 52 other: "moins de %{count}Β minutes"
53 53 x_minutes:
54 54 one: "1Β minute"
55 55 other: "%{count}Β minutes"
56 56 about_x_hours:
57 57 one: "environ une heure"
58 58 other: "environ %{count}Β heures"
59 59 x_hours:
60 60 one: "une heure"
61 61 other: "%{count}Β heures"
62 62 x_days:
63 63 one: "unΒ jour"
64 64 other: "%{count}Β jours"
65 65 about_x_months:
66 66 one: "environ un mois"
67 67 other: "environ %{count}Β mois"
68 68 x_months:
69 69 one: "unΒ mois"
70 70 other: "%{count}Β mois"
71 71 about_x_years:
72 72 one: "environ un an"
73 73 other: "environ %{count}Β ans"
74 74 over_x_years:
75 75 one: "plus d'un an"
76 76 other: "plus de %{count}Β ans"
77 77 almost_x_years:
78 78 one: "presqu'un an"
79 79 other: "presque %{count} ans"
80 80 prompts:
81 81 year: "AnnΓ©e"
82 82 month: "Mois"
83 83 day: "Jour"
84 84 hour: "Heure"
85 85 minute: "Minute"
86 86 second: "Seconde"
87 87
88 88 number:
89 89 format:
90 90 precision: 3
91 91 separator: ','
92 92 delimiter: 'Β '
93 93 currency:
94 94 format:
95 95 unit: '€'
96 96 precision: 2
97 97 format: '%nΒ %u'
98 98 human:
99 99 format:
100 100 precision: 3
101 101 storage_units:
102 102 format: "%n %u"
103 103 units:
104 104 byte:
105 105 one: "octet"
106 106 other: "octets"
107 107 kb: "ko"
108 108 mb: "Mo"
109 109 gb: "Go"
110 110 tb: "To"
111 111
112 112 support:
113 113 array:
114 114 sentence_connector: 'et'
115 115 skip_last_comma: true
116 116 word_connector: ", "
117 117 two_words_connector: " et "
118 118 last_word_connector: " et "
119 119
120 120 activerecord:
121 121 errors:
122 122 template:
123 123 header:
124 124 one: "Impossible d'enregistrer %{model} : une erreur"
125 125 other: "Impossible d'enregistrer %{model} : %{count} erreurs."
126 126 body: "Veuillez vΓ©rifier les champs suivantsΒ :"
127 127 messages:
128 128 inclusion: "n'est pas inclus(e) dans la liste"
129 129 exclusion: "n'est pas disponible"
130 130 invalid: "n'est pas valide"
131 131 confirmation: "ne concorde pas avec la confirmation"
132 132 accepted: "doit Γͺtre acceptΓ©(e)"
133 133 empty: "doit Γͺtre renseignΓ©(e)"
134 134 blank: "doit Γͺtre renseignΓ©(e)"
135 135 too_long: "est trop long (pas plus de %{count} caractères)"
136 136 too_short: "est trop court (au moins %{count} caractères)"
137 137 wrong_length: "ne fait pas la bonne longueur (doit comporter %{count} caractères)"
138 138 taken: "est dΓ©jΓ  utilisΓ©"
139 139 not_a_number: "n'est pas un nombre"
140 140 not_a_date: "n'est pas une date valide"
141 141 greater_than: "doit Γͺtre supΓ©rieur Γ  %{count}"
142 142 greater_than_or_equal_to: "doit Γͺtre supΓ©rieur ou Γ©gal Γ  %{count}"
143 143 equal_to: "doit Γͺtre Γ©gal Γ  %{count}"
144 144 less_than: "doit Γͺtre infΓ©rieur Γ  %{count}"
145 145 less_than_or_equal_to: "doit Γͺtre infΓ©rieur ou Γ©gal Γ  %{count}"
146 146 odd: "doit Γͺtre impair"
147 147 even: "doit Γͺtre pair"
148 148 greater_than_start_date: "doit Γͺtre postΓ©rieure Γ  la date de dΓ©but"
149 149 not_same_project: "n'appartient pas au mΓͺme projet"
150 150 circular_dependency: "Cette relation crΓ©erait une dΓ©pendance circulaire"
151 151 cant_link_an_issue_with_a_descendant: "Une demande ne peut pas Γͺtre liΓ©e Γ  l'une de ses sous-tΓ’ches"
152 152 earlier_than_minimum_start_date: "ne peut pas Γͺtre antΓ©rieure au %{date} Γ  cause des demandes qui prΓ©cΓ¨dent"
153 153
154 154 actionview_instancetag_blank_option: Choisir
155 155
156 156 general_text_No: 'Non'
157 157 general_text_Yes: 'Oui'
158 158 general_text_no: 'non'
159 159 general_text_yes: 'oui'
160 160 general_lang_name: 'French (FranΓ§ais)'
161 161 general_csv_separator: ';'
162 162 general_csv_decimal_separator: ','
163 163 general_csv_encoding: ISO-8859-1
164 164 general_pdf_fontname: freesans
165 165 general_first_day_of_week: '1'
166 166
167 167 notice_account_updated: Le compte a été mis à jour avec succès.
168 168 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
169 169 notice_account_password_updated: Mot de passe mis à jour avec succès.
170 170 notice_account_wrong_password: Mot de passe incorrect
171 171 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a Γ©tΓ© envoyΓ© Γ  l'adresse %{email}.
172 172 notice_account_unknown_email: Aucun compte ne correspond Γ  cette adresse.
173 173 notice_account_not_activated_yet: Vous n'avez pas encore activΓ© votre compte. Si vous voulez recevoir un nouveau message d'activation, veuillez <a href="%{url}">cliquer sur ce lien</a>.
174 174 notice_account_locked: Votre compte est verrouillΓ©.
175 175 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
176 176 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a Γ©tΓ© envoyΓ©.
177 177 notice_account_activated: Votre compte a Γ©tΓ© activΓ©. Vous pouvez Γ  prΓ©sent vous connecter.
178 178 notice_successful_create: Création effectuée avec succès.
179 179 notice_successful_update: Mise à jour effectuée avec succès.
180 180 notice_successful_delete: Suppression effectuée avec succès.
181 181 notice_successful_connection: Connexion rΓ©ussie.
182 182 notice_file_not_found: "La page Γ  laquelle vous souhaitez accΓ©der n'existe pas ou a Γ©tΓ© supprimΓ©e."
183 183 notice_locking_conflict: Les donnΓ©es ont Γ©tΓ© mises Γ  jour par un autre utilisateur. Mise Γ  jour impossible.
184 184 notice_not_authorized: "Vous n'Γͺtes pas autorisΓ© Γ  accΓ©der Γ  cette page."
185 185 notice_not_authorized_archived_project: Le projet auquel vous tentez d'accΓ©der a Γ©tΓ© archivΓ©.
186 186 notice_email_sent: "Un email a Γ©tΓ© envoyΓ© Γ  %{value}"
187 187 notice_email_error: "Erreur lors de l'envoi de l'email (%{value})"
188 188 notice_feeds_access_key_reseted: "Votre clé d'accès aux flux Atom a été réinitialisée."
189 189 notice_api_access_key_reseted: Votre clé d'accès API a été réinitialisée.
190 190 notice_failed_to_save_issues: "%{count} demande(s) sur les %{total} sΓ©lectionnΓ©es n'ont pas pu Γͺtre mise(s) Γ  jour : %{ids}."
191 191 notice_failed_to_save_time_entries: "%{count} temps passΓ©(s) sur les %{total} sΓ©lectionnΓ©s n'ont pas pu Γͺtre mis Γ  jour: %{ids}."
192 192 notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}."
193 193 notice_no_issue_selected: "Aucune demande sΓ©lectionnΓ©e ! Cochez les demandes que vous voulez mettre Γ  jour."
194 194 notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur."
195 195 notice_default_data_loaded: Paramétrage par défaut chargé avec succès.
196 196 notice_unable_delete_version: Impossible de supprimer cette version.
197 197 notice_unable_delete_time_entry: Impossible de supprimer le temps passΓ©.
198 198 notice_issue_done_ratios_updated: L'avancement des demandes a Γ©tΓ© mis Γ  jour.
199 199 notice_gantt_chart_truncated: "Le diagramme a Γ©tΓ© tronquΓ© car il excΓ¨de le nombre maximal d'Γ©lΓ©ments pouvant Γͺtre affichΓ©s (%{max})"
200 200 notice_issue_successful_create: "Demande %{id} créée."
201 201 notice_issue_update_conflict: "La demande a Γ©tΓ© mise Γ  jour par un autre utilisateur pendant que vous la modifiez."
202 202 notice_account_deleted: "Votre compte a Γ©tΓ© dΓ©finitivement supprimΓ©."
203 203 notice_user_successful_create: "Utilisateur %{id} créé."
204 204 notice_new_password_must_be_different: Votre nouveau mot de passe doit Γͺtre diffΓ©rent de votre mot de passe actuel
205 205 notice_import_finished: "Les %{count} Γ©lΓ©ments ont Γ©tΓ© importΓ©(s)."
206 206 notice_import_finished_with_errors: "%{count} Γ©lΓ©ment(s) sur %{total} n'ont pas pu Γͺtre importΓ©(s)."
207 207
208 208 error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramΓ©trage : %{value}"
209 209 error_scm_not_found: "L'entrΓ©e et/ou la rΓ©vision demandΓ©e n'existe pas dans le dΓ©pΓ΄t."
210 210 error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt : %{value}"
211 211 error_scm_annotate: "L'entrΓ©e n'existe pas ou ne peut pas Γͺtre annotΓ©e."
212 212 error_scm_annotate_big_text_file: Cette entrΓ©e ne peut pas Γͺtre annotΓ©e car elle excΓ¨de la taille maximale.
213 213 error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ  ce projet"
214 214 error_no_tracker_in_project: "Aucun tracker n'est associΓ© Γ  ce projet. VΓ©rifier la configuration du projet."
215 215 error_no_default_issue_status: "Aucun statut de demande n'est dΓ©fini par dΓ©faut. VΓ©rifier votre configuration (Administration -> Statuts de demandes)."
216 216 error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisΓ©
217 217 error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas Γͺtre supprimΓ©.
218 218 error_can_not_remove_role: Ce rΓ΄le est utilisΓ© et ne peut pas Γͺtre supprimΓ©.
219 219 error_can_not_reopen_issue_on_closed_version: 'Une demande assignΓ©e Γ  une version fermΓ©e ne peut pas Γͺtre rΓ©ouverte'
220 220 error_can_not_archive_project: "Ce projet ne peut pas Γͺtre archivΓ©"
221 221 error_issue_done_ratios_not_updated: L'avancement des demandes n'a pas pu Γͺtre mis Γ  jour.
222 222 error_workflow_copy_source: 'Veuillez sΓ©lectionner un tracker et/ou un rΓ΄le source'
223 223 error_workflow_copy_target: 'Veuillez sΓ©lectionner les trackers et rΓ΄les cibles'
224 224 error_unable_delete_issue_status: Impossible de supprimer le statut de demande
225 225 error_unable_to_connect: Connexion impossible (%{value})
226 226 error_attachment_too_big: Ce fichier ne peut pas Γͺtre attachΓ© car il excΓ¨de la taille maximale autorisΓ©e (%{max_size})
227 227 error_session_expired: "Votre session a expirΓ©. Veuillez vous reconnecter."
228 228 warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu Γͺtre sauvegardΓ©s."
229 229 error_password_expired: "Votre mot de passe a expirΓ© ou nΓ©cessite d'Γͺtre changΓ©."
230 230 error_invalid_file_encoding: "Le fichier n'est pas un fichier %{encoding} valide"
231 231 error_invalid_csv_file_or_settings: "Le fichier n'est pas un fichier CSV ou n'est pas conforme aux paramètres sélectionnés"
232 232 error_can_not_read_import_file: "Une erreur est survenue lors de la lecture du fichier Γ  importer"
233 233 error_attachment_extension_not_allowed: "L'extension %{extension} n'est pas autorisΓ©e"
234 234
235 235 mail_subject_lost_password: "Votre mot de passe %{value}"
236 236 mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant :'
237 237 mail_subject_register: "Activation de votre compte %{value}"
238 238 mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant :'
239 239 mail_body_account_information_external: "Vous pouvez utiliser votre compte %{value} pour vous connecter."
240 240 mail_body_account_information: Paramètres de connexion de votre compte
241 241 mail_subject_account_activation_request: "Demande d'activation d'un compte %{value}"
242 242 mail_body_account_activation_request: "Un nouvel utilisateur (%{value}) s'est inscrit. Son compte nΓ©cessite votre approbation :"
243 243 mail_subject_reminder: "%{count} demande(s) arrivent Γ  Γ©chΓ©ance (%{days})"
244 244 mail_body_reminder: "%{count} demande(s) qui vous sont assignΓ©es arrivent Γ  Γ©chΓ©ance dans les %{days} prochains jours :"
245 245 mail_subject_wiki_content_added: "Page wiki '%{id}' ajoutΓ©e"
246 246 mail_body_wiki_content_added: "La page wiki '%{id}' a Γ©tΓ© ajoutΓ©e par %{author}."
247 247 mail_subject_wiki_content_updated: "Page wiki '%{id}' mise Γ  jour"
248 248 mail_body_wiki_content_updated: "La page wiki '%{id}' a Γ©tΓ© mise Γ  jour par %{author}."
249 249
250 250 field_name: Nom
251 251 field_description: Description
252 252 field_summary: RΓ©sumΓ©
253 253 field_is_required: Obligatoire
254 254 field_firstname: PrΓ©nom
255 255 field_lastname: Nom
256 256 field_mail: Email
257 257 field_address: Email
258 258 field_filename: Fichier
259 259 field_filesize: Taille
260 260 field_downloads: TΓ©lΓ©chargements
261 261 field_author: Auteur
262 262 field_created_on: Créé
263 263 field_updated_on: Mis-Γ -jour
264 264 field_closed_on: FermΓ©
265 265 field_field_format: Format
266 266 field_is_for_all: Pour tous les projets
267 267 field_possible_values: Valeurs possibles
268 268 field_regexp: Expression régulière
269 269 field_min_length: Longueur minimum
270 270 field_max_length: Longueur maximum
271 271 field_value: Valeur
272 272 field_category: CatΓ©gorie
273 273 field_title: Titre
274 274 field_project: Projet
275 275 field_issue: Demande
276 276 field_status: Statut
277 277 field_notes: Notes
278 278 field_is_closed: Demande fermΓ©e
279 279 field_is_default: Valeur par dΓ©faut
280 280 field_tracker: Tracker
281 281 field_subject: Sujet
282 282 field_due_date: EchΓ©ance
283 283 field_assigned_to: AssignΓ© Γ 
284 284 field_priority: PrioritΓ©
285 285 field_fixed_version: Version cible
286 286 field_user: Utilisateur
287 287 field_principal: Principal
288 288 field_role: RΓ΄le
289 289 field_homepage: Site web
290 290 field_is_public: Public
291 291 field_parent: Sous-projet de
292 292 field_is_in_roadmap: Demandes affichΓ©es dans la roadmap
293 293 field_login: Identifiant
294 294 field_mail_notification: Notifications par mail
295 295 field_admin: Administrateur
296 296 field_last_login_on: Dernière connexion
297 297 field_language: Langue
298 298 field_effective_date: Date
299 299 field_password: Mot de passe
300 300 field_new_password: Nouveau mot de passe
301 301 field_password_confirmation: Confirmation
302 302 field_version: Version
303 303 field_type: Type
304 304 field_host: HΓ΄te
305 305 field_port: Port
306 306 field_account: Compte
307 307 field_base_dn: Base DN
308 308 field_attr_login: Attribut Identifiant
309 309 field_attr_firstname: Attribut PrΓ©nom
310 310 field_attr_lastname: Attribut Nom
311 311 field_attr_mail: Attribut Email
312 312 field_onthefly: CrΓ©ation des utilisateurs Γ  la volΓ©e
313 313 field_start_date: DΓ©but
314 314 field_done_ratio: "% rΓ©alisΓ©"
315 315 field_auth_source: Mode d'authentification
316 316 field_hide_mail: Cacher mon adresse mail
317 317 field_comments: Commentaire
318 318 field_url: URL
319 319 field_start_page: Page de dΓ©marrage
320 320 field_subproject: Sous-projet
321 321 field_hours: Heures
322 322 field_activity: ActivitΓ©
323 323 field_spent_on: Date
324 324 field_identifier: Identifiant
325 325 field_is_filter: UtilisΓ© comme filtre
326 326 field_issue_to: Demande liΓ©e
327 327 field_delay: Retard
328 328 field_assignable: Demandes assignables Γ  ce rΓ΄le
329 329 field_redirect_existing_links: Rediriger les liens existants
330 330 field_estimated_hours: Temps estimΓ©
331 331 field_column_names: Colonnes
332 332 field_time_entries: Temps passΓ©
333 333 field_time_zone: Fuseau horaire
334 334 field_searchable: UtilisΓ© pour les recherches
335 335 field_default_value: Valeur par dΓ©faut
336 336 field_comments_sorting: Afficher les commentaires
337 337 field_parent_title: Page parent
338 338 field_editable: Modifiable
339 339 field_watcher: Observateur
340 340 field_identity_url: URL OpenID
341 341 field_content: Contenu
342 342 field_group_by: Grouper par
343 343 field_sharing: Partage
344 344 field_parent_issue: TΓ’che parente
345 345 field_member_of_group: Groupe de l'assignΓ©
346 346 field_assigned_to_role: RΓ΄le de l'assignΓ©
347 347 field_text: Champ texte
348 348 field_visible: Visible
349 349 field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardΓ©"
350 350 field_issues_visibility: VisibilitΓ© des demandes
351 351 field_is_private: PrivΓ©e
352 352 field_commit_logs_encoding: Encodage des messages de commit
353 353 field_scm_path_encoding: Encodage des chemins
354 354 field_path_to_repository: Chemin du dΓ©pΓ΄t
355 355 field_root_directory: RΓ©pertoire racine
356 356 field_cvsroot: CVSROOT
357 357 field_cvs_module: Module
358 358 field_repository_is_default: DΓ©pΓ΄t principal
359 359 field_multiple: Valeurs multiples
360 360 field_auth_source_ldap_filter: Filtre LDAP
361 361 field_core_fields: Champs standards
362 362 field_timeout: "Timeout (en secondes)"
363 363 field_board_parent: Forum parent
364 364 field_private_notes: Notes privΓ©es
365 365 field_inherit_members: HΓ©riter les membres
366 366 field_generate_password: GΓ©nΓ©rer un mot de passe
367 367 field_must_change_passwd: Doit changer de mot de passe Γ  la prochaine connexion
368 368 field_default_status: Statut par dΓ©faut
369 369 field_users_visibility: VisibilitΓ© des utilisateurs
370 370 field_time_entries_visibility: VisibilitΓ© du temps passΓ©
371 371 field_total_estimated_hours: Temps estimΓ© total
372 372 field_default_version: Version par dΓ©faut
373 373
374 374 setting_app_title: Titre de l'application
375 375 setting_app_subtitle: Sous-titre de l'application
376 376 setting_welcome_text: Texte d'accueil
377 377 setting_default_language: Langue par dΓ©faut
378 378 setting_login_required: Authentification obligatoire
379 379 setting_self_registration: Inscription des nouveaux utilisateurs
380 380 setting_attachment_max_size: Taille maximale des fichiers
381 381 setting_issues_export_limit: Limite d'exportation des demandes
382 382 setting_mail_from: Adresse d'Γ©mission
383 383 setting_bcc_recipients: Destinataires en copie cachΓ©e (cci)
384 384 setting_plain_text_mail: Mail en texte brut (non HTML)
385 385 setting_host_name: Nom d'hΓ΄te et chemin
386 386 setting_text_formatting: Formatage du texte
387 387 setting_wiki_compression: Compression de l'historique des pages wiki
388 388 setting_feeds_limit: Nombre maximal d'Γ©lΓ©ments dans les flux Atom
389 389 setting_default_projects_public: DΓ©finir les nouveaux projets comme publics par dΓ©faut
390 390 setting_autofetch_changesets: RΓ©cupΓ©ration automatique des commits
391 391 setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts
392 392 setting_commit_ref_keywords: Mots-clΓ©s de rΓ©fΓ©rencement
393 393 setting_commit_fix_keywords: Mots-clΓ©s de rΓ©solution
394 394 setting_autologin: DurΓ©e maximale de connexion automatique
395 395 setting_date_format: Format de date
396 396 setting_time_format: Format d'heure
397 397 setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets
398 398 setting_cross_project_subtasks: Autoriser les sous-tΓ’ches dans des projets diffΓ©rents
399 399 setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes
400 400 setting_repositories_encodings: Encodages des fichiers et des dΓ©pΓ΄ts
401 401 setting_emails_header: En-tΓͺte des emails
402 402 setting_emails_footer: Pied-de-page des emails
403 403 setting_protocol: Protocole
404 404 setting_per_page_options: Options d'objets affichΓ©s par page
405 405 setting_user_format: Format d'affichage des utilisateurs
406 406 setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets
407 407 setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux
408 408 setting_enabled_scm: SCM activΓ©s
409 409 setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes"
410 410 setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails"
411 411 setting_mail_handler_api_key: ClΓ© de protection de l'API
412 412 setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels
413 413 setting_gravatar_enabled: Afficher les Gravatar des utilisateurs
414 414 setting_gravatar_default: Image Gravatar par dΓ©faut
415 415 setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es
416 416 setting_file_max_size_displayed: Taille maximum des fichiers texte affichΓ©s en ligne
417 417 setting_repository_log_display_limit: "Nombre maximum de rΓ©visions affichΓ©es sur l'historique d'un fichier"
418 418 setting_openid: "Autoriser l'authentification et l'enregistrement OpenID"
419 419 setting_password_max_age: Expiration des mots de passe après
420 420 setting_password_min_length: Longueur minimum des mots de passe
421 421 setting_new_project_user_role_id: RΓ΄le donnΓ© Γ  un utilisateur non-administrateur qui crΓ©e un projet
422 422 setting_default_projects_modules: Modules activΓ©s par dΓ©faut pour les nouveaux projets
423 423 setting_issue_done_ratio: Calcul de l'avancement des demandes
424 424 setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectuΓ©'
425 425 setting_issue_done_ratio_issue_status: Utiliser le statut
426 426 setting_start_of_week: Jour de dΓ©but des calendriers
427 427 setting_rest_api_enabled: Activer l'API REST
428 428 setting_cache_formatted_text: Mettre en cache le texte formatΓ©
429 429 setting_default_notification_option: Option de notification par dΓ©faut
430 430 setting_commit_logtime_enabled: Permettre la saisie de temps
431 431 setting_commit_logtime_activity_id: ActivitΓ© pour le temps saisi
432 432 setting_gantt_items_limit: Nombre maximum d'Γ©lΓ©ments affichΓ©s sur le gantt
433 433 setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes
434 434 setting_default_issue_start_date_to_creation_date: Donner Γ  la date de dΓ©but d'une nouvelle demande la valeur de la date du jour
435 435 setting_commit_cross_project_ref: Permettre le rΓ©fΓ©rencement et la rΓ©solution des demandes de tous les autres projets
436 436 setting_unsubscribe: Permettre aux utilisateurs de supprimer leur propre compte
437 437 setting_session_lifetime: DurΓ©e de vie maximale des sessions
438 438 setting_session_timeout: DurΓ©e maximale d'inactivitΓ©
439 439 setting_thumbnails_enabled: Afficher les vignettes des images
440 440 setting_thumbnails_size: Taille des vignettes (en pixels)
441 441 setting_non_working_week_days: Jours non travaillΓ©s
442 442 setting_jsonp_enabled: Activer le support JSONP
443 443 setting_default_projects_tracker_ids: Trackers par dΓ©faut pour les nouveaux projets
444 444 setting_mail_handler_excluded_filenames: Exclure les fichiers attachΓ©s par leur nom
445 445 setting_force_default_language_for_anonymous: Forcer la langue par dΓ©fault pour les utilisateurs anonymes
446 446 setting_force_default_language_for_loggedin: Forcer la langue par dΓ©fault pour les utilisateurs identifiΓ©s
447 447 setting_link_copied_issue: Lier les demandes lors de la copie
448 448 setting_max_additional_emails: Nombre maximal d'adresses email additionnelles
449 449 setting_search_results_per_page: RΓ©sultats de recherche affichΓ©s par page
450 450 setting_attachment_extensions_allowed: Extensions autorisΓ©es
451 451 setting_attachment_extensions_denied: Extensions non autorisΓ©es
452 452
453 453 permission_add_project: CrΓ©er un projet
454 454 permission_add_subprojects: CrΓ©er des sous-projets
455 455 permission_edit_project: Modifier le projet
456 456 permission_close_project: Fermer / rΓ©ouvrir le projet
457 457 permission_select_project_modules: Choisir les modules
458 458 permission_manage_members: GΓ©rer les membres
459 459 permission_manage_project_activities: GΓ©rer les activitΓ©s
460 460 permission_manage_versions: GΓ©rer les versions
461 461 permission_manage_categories: GΓ©rer les catΓ©gories de demandes
462 462 permission_view_issues: Voir les demandes
463 463 permission_add_issues: CrΓ©er des demandes
464 464 permission_edit_issues: Modifier les demandes
465 465 permission_copy_issues: Copier les demandes
466 466 permission_manage_issue_relations: GΓ©rer les relations
467 467 permission_set_issues_private: Rendre les demandes publiques ou privΓ©es
468 468 permission_set_own_issues_private: Rendre ses propres demandes publiques ou privΓ©es
469 469 permission_add_issue_notes: Ajouter des notes
470 470 permission_edit_issue_notes: Modifier les notes
471 471 permission_edit_own_issue_notes: Modifier ses propres notes
472 472 permission_view_private_notes: Voir les notes privΓ©es
473 473 permission_set_notes_private: Rendre les notes privΓ©es
474 474 permission_move_issues: DΓ©placer les demandes
475 475 permission_delete_issues: Supprimer les demandes
476 476 permission_manage_public_queries: GΓ©rer les requΓͺtes publiques
477 477 permission_save_queries: Sauvegarder les requΓͺtes
478 478 permission_view_gantt: Voir le gantt
479 479 permission_view_calendar: Voir le calendrier
480 480 permission_view_issue_watchers: Voir la liste des observateurs
481 481 permission_add_issue_watchers: Ajouter des observateurs
482 482 permission_delete_issue_watchers: Supprimer des observateurs
483 483 permission_log_time: Saisir le temps passΓ©
484 484 permission_view_time_entries: Voir le temps passΓ©
485 485 permission_edit_time_entries: Modifier les temps passΓ©s
486 486 permission_edit_own_time_entries: Modifier son propre temps passΓ©
487 487 permission_manage_news: GΓ©rer les annonces
488 488 permission_comment_news: Commenter les annonces
489 489 permission_view_documents: Voir les documents
490 490 permission_add_documents: Ajouter des documents
491 491 permission_edit_documents: Modifier les documents
492 492 permission_delete_documents: Supprimer les documents
493 493 permission_manage_files: GΓ©rer les fichiers
494 494 permission_view_files: Voir les fichiers
495 495 permission_manage_wiki: GΓ©rer le wiki
496 496 permission_rename_wiki_pages: Renommer les pages
497 497 permission_delete_wiki_pages: Supprimer les pages
498 498 permission_view_wiki_pages: Voir le wiki
499 499 permission_view_wiki_edits: "Voir l'historique des modifications"
500 500 permission_edit_wiki_pages: Modifier les pages
501 501 permission_delete_wiki_pages_attachments: Supprimer les fichiers joints
502 502 permission_protect_wiki_pages: ProtΓ©ger les pages
503 503 permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources
504 504 permission_browse_repository: Parcourir les sources
505 505 permission_view_changesets: Voir les rΓ©visions
506 506 permission_commit_access: Droit de commit
507 507 permission_manage_boards: GΓ©rer les forums
508 508 permission_view_messages: Voir les messages
509 509 permission_add_messages: Poster un message
510 510 permission_edit_messages: Modifier les messages
511 511 permission_edit_own_messages: Modifier ses propres messages
512 512 permission_delete_messages: Supprimer les messages
513 513 permission_delete_own_messages: Supprimer ses propres messages
514 514 permission_export_wiki_pages: Exporter les pages
515 515 permission_manage_subtasks: GΓ©rer les sous-tΓ’ches
516 516 permission_manage_related_issues: GΓ©rer les demandes associΓ©es
517 517 permission_import_issues: Importer des demandes
518 518
519 519 project_module_issue_tracking: Suivi des demandes
520 520 project_module_time_tracking: Suivi du temps passΓ©
521 521 project_module_news: Publication d'annonces
522 522 project_module_documents: Publication de documents
523 523 project_module_files: Publication de fichiers
524 524 project_module_wiki: Wiki
525 525 project_module_repository: DΓ©pΓ΄t de sources
526 526 project_module_boards: Forums de discussion
527 527 project_module_calendar: Calendrier
528 528 project_module_gantt: Gantt
529 529
530 530 label_user: Utilisateur
531 531 label_user_plural: Utilisateurs
532 532 label_user_new: Nouvel utilisateur
533 533 label_user_anonymous: Anonyme
534 534 label_project: Projet
535 535 label_project_new: Nouveau projet
536 536 label_project_plural: Projets
537 537 label_x_projects:
538 538 zero: aucun projet
539 539 one: un projet
540 540 other: "%{count} projets"
541 541 label_project_all: Tous les projets
542 542 label_project_latest: Derniers projets
543 543 label_issue: Demande
544 544 label_issue_new: Nouvelle demande
545 545 label_issue_plural: Demandes
546 546 label_issue_view_all: Voir toutes les demandes
547 547 label_issues_by: "Demandes par %{value}"
548 548 label_issue_added: Demande ajoutΓ©e
549 549 label_issue_updated: Demande mise Γ  jour
550 550 label_issue_note_added: Note ajoutΓ©e
551 551 label_issue_status_updated: Statut changΓ©
552 552 label_issue_assigned_to_updated: AssignΓ© changΓ©
553 553 label_issue_priority_updated: PrioritΓ© changΓ©e
554 554 label_document: Document
555 555 label_document_new: Nouveau document
556 556 label_document_plural: Documents
557 557 label_document_added: Document ajoutΓ©
558 558 label_role: RΓ΄le
559 559 label_role_plural: RΓ΄les
560 560 label_role_new: Nouveau rΓ΄le
561 561 label_role_and_permissions: RΓ΄les et permissions
562 562 label_role_anonymous: Anonyme
563 563 label_role_non_member: Non membre
564 564 label_member: Membre
565 565 label_member_new: Nouveau membre
566 566 label_member_plural: Membres
567 567 label_tracker: Tracker
568 568 label_tracker_plural: Trackers
569 569 label_tracker_new: Nouveau tracker
570 570 label_workflow: Workflow
571 571 label_issue_status: Statut de demandes
572 572 label_issue_status_plural: Statuts de demandes
573 573 label_issue_status_new: Nouveau statut
574 574 label_issue_category: CatΓ©gorie de demandes
575 575 label_issue_category_plural: CatΓ©gories de demandes
576 576 label_issue_category_new: Nouvelle catΓ©gorie
577 577 label_custom_field: Champ personnalisΓ©
578 578 label_custom_field_plural: Champs personnalisΓ©s
579 579 label_custom_field_new: Nouveau champ personnalisΓ©
580 580 label_enumerations: Listes de valeurs
581 581 label_enumeration_new: Nouvelle valeur
582 582 label_information: Information
583 583 label_information_plural: Informations
584 584 label_please_login: Identification
585 585 label_register: S'enregistrer
586 586 label_login_with_open_id_option: S'authentifier avec OpenID
587 587 label_password_lost: Mot de passe perdu
588 588 label_password_required: Confirmez votre mot de passe pour continuer
589 589 label_home: Accueil
590 590 label_my_page: Ma page
591 591 label_my_account: Mon compte
592 592 label_my_projects: Mes projets
593 593 label_my_page_block: Blocs disponibles
594 594 label_administration: Administration
595 595 label_login: Connexion
596 596 label_logout: DΓ©connexion
597 597 label_help: Aide
598 598 label_reported_issues: Demandes soumises
599 599 label_assigned_issues: Demandes assignΓ©es
600 600 label_assigned_to_me_issues: Demandes qui me sont assignΓ©es
601 601 label_last_login: Dernière connexion
602 602 label_registered_on: Inscrit le
603 603 label_activity: ActivitΓ©
604 604 label_overall_activity: ActivitΓ© globale
605 605 label_user_activity: "ActivitΓ© de %{value}"
606 606 label_new: Nouveau
607 607 label_logged_as: ConnectΓ© en tant que
608 608 label_environment: Environnement
609 609 label_authentication: Authentification
610 610 label_auth_source: Mode d'authentification
611 611 label_auth_source_new: Nouveau mode d'authentification
612 612 label_auth_source_plural: Modes d'authentification
613 613 label_subproject_plural: Sous-projets
614 614 label_subproject_new: Nouveau sous-projet
615 615 label_and_its_subprojects: "%{value} et ses sous-projets"
616 616 label_min_max_length: Longueurs mini - maxi
617 617 label_list: Liste
618 618 label_date: Date
619 619 label_integer: Entier
620 620 label_float: Nombre dΓ©cimal
621 621 label_boolean: BoolΓ©en
622 622 label_string: Texte
623 623 label_text: Texte long
624 624 label_attribute: Attribut
625 625 label_attribute_plural: Attributs
626 626 label_no_data: Aucune donnΓ©e Γ  afficher
627 627 label_change_status: Changer le statut
628 628 label_history: Historique
629 629 label_attachment: Fichier
630 630 label_attachment_new: Nouveau fichier
631 631 label_attachment_delete: Supprimer le fichier
632 632 label_attachment_plural: Fichiers
633 633 label_file_added: Fichier ajoutΓ©
634 634 label_report: Rapport
635 635 label_report_plural: Rapports
636 636 label_news: Annonce
637 637 label_news_new: Nouvelle annonce
638 638 label_news_plural: Annonces
639 639 label_news_latest: Dernières annonces
640 640 label_news_view_all: Voir toutes les annonces
641 641 label_news_added: Annonce ajoutΓ©e
642 642 label_news_comment_added: Commentaire ajoutΓ© Γ  une annonce
643 643 label_settings: Configuration
644 644 label_overview: AperΓ§u
645 645 label_version: Version
646 646 label_version_new: Nouvelle version
647 647 label_version_plural: Versions
648 648 label_close_versions: Fermer les versions terminΓ©es
649 649 label_confirmation: Confirmation
650 650 label_export_to: 'Formats disponibles :'
651 651 label_read: Lire...
652 652 label_public_projects: Projets publics
653 653 label_open_issues: ouvert
654 654 label_open_issues_plural: ouverts
655 655 label_closed_issues: fermΓ©
656 656 label_closed_issues_plural: fermΓ©s
657 657 label_x_open_issues_abbr:
658 658 zero: 0 ouverte
659 659 one: 1 ouverte
660 660 other: "%{count} ouvertes"
661 661 label_x_closed_issues_abbr:
662 662 zero: 0 fermΓ©e
663 663 one: 1 fermΓ©e
664 664 other: "%{count} fermΓ©es"
665 665 label_x_issues:
666 666 zero: 0 demande
667 667 one: 1 demande
668 668 other: "%{count} demandes"
669 669 label_total: Total
670 670 label_total_plural: Totaux
671 671 label_total_time: Temps total
672 672 label_permissions: Permissions
673 673 label_current_status: Statut actuel
674 674 label_new_statuses_allowed: Nouveaux statuts autorisΓ©s
675 675 label_all: tous
676 676 label_any: tous
677 677 label_none: aucun
678 678 label_nobody: personne
679 679 label_next: Suivant
680 680 label_previous: PrΓ©cΓ©dent
681 681 label_used_by: UtilisΓ© par
682 682 label_details: DΓ©tails
683 683 label_add_note: Ajouter une note
684 684 label_calendar: Calendrier
685 685 label_months_from: mois depuis
686 686 label_gantt: Gantt
687 687 label_internal: Interne
688 688 label_last_changes: "%{count} derniers changements"
689 689 label_change_view_all: Voir tous les changements
690 690 label_personalize_page: Personnaliser cette page
691 691 label_comment: Commentaire
692 692 label_comment_plural: Commentaires
693 693 label_x_comments:
694 694 zero: aucun commentaire
695 695 one: un commentaire
696 696 other: "%{count} commentaires"
697 697 label_comment_add: Ajouter un commentaire
698 698 label_comment_added: Commentaire ajoutΓ©
699 699 label_comment_delete: Supprimer les commentaires
700 700 label_query: Rapport personnalisΓ©
701 701 label_query_plural: Rapports personnalisΓ©s
702 702 label_query_new: Nouveau rapport
703 703 label_my_queries: Mes rapports personnalisΓ©s
704 704 label_filter_add: Ajouter le filtre
705 705 label_filter_plural: Filtres
706 706 label_equals: Γ©gal
707 707 label_not_equals: diffΓ©rent
708 708 label_in_less_than: dans moins de
709 709 label_in_more_than: dans plus de
710 710 label_in_the_next_days: dans les prochains jours
711 711 label_in_the_past_days: dans les derniers jours
712 712 label_greater_or_equal: '>='
713 713 label_less_or_equal: '<='
714 714 label_between: entre
715 715 label_in: dans
716 716 label_today: aujourd'hui
717 717 label_all_time: toute la pΓ©riode
718 718 label_yesterday: hier
719 719 label_this_week: cette semaine
720 720 label_last_week: la semaine dernière
721 721 label_last_n_weeks: "les %{count} dernières semaines"
722 722 label_last_n_days: "les %{count} derniers jours"
723 723 label_this_month: ce mois-ci
724 724 label_last_month: le mois dernier
725 725 label_this_year: cette annΓ©e
726 726 label_date_range: PΓ©riode
727 727 label_less_than_ago: il y a moins de
728 728 label_more_than_ago: il y a plus de
729 729 label_ago: il y a
730 730 label_contains: contient
731 731 label_not_contains: ne contient pas
732 732 label_any_issues_in_project: une demande du projet
733 733 label_any_issues_not_in_project: une demande hors du projet
734 734 label_no_issues_in_project: aucune demande du projet
735 label_any_open_issues: une demande ouverte
736 label_no_open_issues: aucune demande ouverte
735 737 label_day_plural: jours
736 738 label_repository: DΓ©pΓ΄t
737 739 label_repository_new: Nouveau dΓ©pΓ΄t
738 740 label_repository_plural: DΓ©pΓ΄ts
739 741 label_browse: Parcourir
740 742 label_branch: Branche
741 743 label_tag: Tag
742 744 label_revision: RΓ©vision
743 745 label_revision_plural: RΓ©visions
744 746 label_revision_id: "RΓ©vision %{value}"
745 747 label_associated_revisions: RΓ©visions associΓ©es
746 748 label_added: ajoutΓ©
747 749 label_modified: modifiΓ©
748 750 label_copied: copiΓ©
749 751 label_renamed: renommΓ©
750 752 label_deleted: supprimΓ©
751 753 label_latest_revision: Dernière révision
752 754 label_latest_revision_plural: Dernières révisions
753 755 label_view_revisions: Voir les rΓ©visions
754 756 label_view_all_revisions: Voir toutes les rΓ©visions
755 757 label_max_size: Taille maximale
756 758 label_sort_highest: Remonter en premier
757 759 label_sort_higher: Remonter
758 760 label_sort_lower: Descendre
759 761 label_sort_lowest: Descendre en dernier
760 762 label_roadmap: Roadmap
761 763 label_roadmap_due_in: "Γ‰chΓ©ance dans %{value}"
762 764 label_roadmap_overdue: "En retard de %{value}"
763 765 label_roadmap_no_issues: Aucune demande pour cette version
764 766 label_search: Recherche
765 767 label_result_plural: RΓ©sultats
766 768 label_all_words: Tous les mots
767 769 label_wiki: Wiki
768 770 label_wiki_edit: RΓ©vision wiki
769 771 label_wiki_edit_plural: RΓ©visions wiki
770 772 label_wiki_page: Page wiki
771 773 label_wiki_page_plural: Pages wiki
772 774 label_index_by_title: Index par titre
773 775 label_index_by_date: Index par date
774 776 label_current_version: Version actuelle
775 777 label_preview: PrΓ©visualisation
776 778 label_feed_plural: Flux Atom
777 779 label_changes_details: DΓ©tails de tous les changements
778 780 label_issue_tracking: Suivi des demandes
779 781 label_spent_time: Temps passΓ©
780 782 label_total_spent_time: Temps passΓ© total
781 783 label_overall_spent_time: Temps passΓ© global
782 784 label_f_hour: "%{value} heure"
783 785 label_f_hour_plural: "%{value} heures"
784 786 label_f_hour_short: "%{value} h"
785 787 label_time_tracking: Suivi du temps
786 788 label_change_plural: Changements
787 789 label_statistics: Statistiques
788 790 label_commits_per_month: Commits par mois
789 791 label_commits_per_author: Commits par auteur
790 792 label_diff: diff
791 793 label_view_diff: Voir les diffΓ©rences
792 794 label_diff_inline: en ligne
793 795 label_diff_side_by_side: cΓ΄te Γ  cΓ΄te
794 796 label_options: Options
795 797 label_copy_workflow_from: Copier le workflow de
796 798 label_permissions_report: Synthèse des permissions
797 799 label_watched_issues: Demandes surveillΓ©es
798 800 label_related_issues: Demandes liΓ©es
799 801 label_applied_status: Statut appliquΓ©
800 802 label_loading: Chargement...
801 803 label_relation_new: Nouvelle relation
802 804 label_relation_delete: Supprimer la relation
803 805 label_relates_to: LiΓ© Γ 
804 806 label_duplicates: Duplique
805 807 label_duplicated_by: DupliquΓ© par
806 808 label_blocks: Bloque
807 809 label_blocked_by: BloquΓ© par
808 810 label_precedes: Précède
809 811 label_follows: Suit
810 812 label_copied_to: CopiΓ© vers
811 813 label_copied_from: CopiΓ© depuis
812 814 label_end_to_start: fin Γ  dΓ©but
813 815 label_end_to_end: fin Γ  fin
814 816 label_start_to_start: dΓ©but Γ  dΓ©but
815 817 label_start_to_end: dΓ©but Γ  fin
816 818 label_stay_logged_in: Rester connectΓ©
817 819 label_disabled: dΓ©sactivΓ©
818 820 label_show_completed_versions: Voir les versions passΓ©es
819 821 label_me: moi
820 822 label_board: Forum
821 823 label_board_new: Nouveau forum
822 824 label_board_plural: Forums
823 825 label_board_locked: VerrouillΓ©
824 826 label_board_sticky: Sticky
825 827 label_topic_plural: Discussions
826 828 label_message_plural: Messages
827 829 label_message_last: Dernier message
828 830 label_message_new: Nouveau message
829 831 label_message_posted: Message ajoutΓ©
830 832 label_reply_plural: RΓ©ponses
831 833 label_send_information: Envoyer les informations Γ  l'utilisateur
832 834 label_year: AnnΓ©e
833 835 label_month: Mois
834 836 label_week: Semaine
835 837 label_date_from: Du
836 838 label_date_to: Au
837 839 label_language_based: BasΓ© sur la langue de l'utilisateur
838 840 label_sort_by: "Trier par %{value}"
839 841 label_send_test_email: Envoyer un email de test
840 842 label_feeds_access_key: Clé d'accès Atom
841 843 label_missing_feeds_access_key: Clé d'accès Atom manquante
842 844 label_feeds_access_key_created_on: "Clé d'accès Atom créée il y a %{value}"
843 845 label_module_plural: Modules
844 846 label_added_time_by: "AjoutΓ© par %{author} il y a %{age}"
845 847 label_updated_time_by: "Mis Γ  jour par %{author} il y a %{age}"
846 848 label_updated_time: "Mis Γ  jour il y a %{value}"
847 849 label_jump_to_a_project: Aller Γ  un projet...
848 850 label_file_plural: Fichiers
849 851 label_changeset_plural: RΓ©visions
850 852 label_default_columns: Colonnes par dΓ©faut
851 853 label_no_change_option: (Pas de changement)
852 854 label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es
853 855 label_bulk_edit_selected_time_entries: Modifier les temps passΓ©s sΓ©lectionnΓ©s
854 856 label_theme: Thème
855 857 label_default: DΓ©faut
856 858 label_search_titles_only: Uniquement dans les titres
857 859 label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets"
858 860 label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..."
859 861 label_user_mail_option_none: Aucune notification
860 862 label_user_mail_option_only_my_events: Seulement pour ce que je surveille
861 863 label_user_mail_option_only_assigned: Seulement pour ce qui m'est assignΓ©
862 864 label_user_mail_option_only_owner: Seulement pour ce que j'ai créé
863 865 label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue"
864 866 label_registration_activation_by_email: activation du compte par email
865 867 label_registration_manual_activation: activation manuelle du compte
866 868 label_registration_automatic_activation: activation automatique du compte
867 869 label_display_per_page: "Par page : %{value}"
868 870 label_age: Γ‚ge
869 871 label_change_properties: Changer les propriΓ©tΓ©s
870 872 label_general: GΓ©nΓ©ral
871 873 label_more: Plus
872 874 label_scm: SCM
873 875 label_plugins: Plugins
874 876 label_ldap_authentication: Authentification LDAP
875 877 label_downloads_abbr: D/L
876 878 label_optional_description: Description facultative
877 879 label_add_another_file: Ajouter un autre fichier
878 880 label_preferences: PrΓ©fΓ©rences
879 881 label_chronological_order: Dans l'ordre chronologique
880 882 label_reverse_chronological_order: Dans l'ordre chronologique inverse
881 883 label_planning: Planning
882 884 label_incoming_emails: Emails entrants
883 885 label_generate_key: GΓ©nΓ©rer une clΓ©
884 886 label_issue_watchers: Observateurs
885 887 label_example: Exemple
886 888 label_display: Affichage
887 889 label_sort: Tri
888 890 label_ascending: Croissant
889 891 label_descending: DΓ©croissant
890 892 label_date_from_to: Du %{start} au %{end}
891 893 label_wiki_content_added: Page wiki ajoutΓ©e
892 894 label_wiki_content_updated: Page wiki mise Γ  jour
893 895 label_group: Groupe
894 896 label_group_plural: Groupes
895 897 label_group_new: Nouveau groupe
896 898 label_group_anonymous: Utilisateurs anonymes
897 899 label_group_non_member: Utilisateurs non membres
898 900 label_time_entry_plural: Temps passΓ©
899 901 label_version_sharing_none: Non partagΓ©
900 902 label_version_sharing_descendants: Avec les sous-projets
901 903 label_version_sharing_hierarchy: Avec toute la hiΓ©rarchie
902 904 label_version_sharing_tree: Avec tout l'arbre
903 905 label_version_sharing_system: Avec tous les projets
904 906 label_update_issue_done_ratios: Mettre Γ  jour l'avancement des demandes
905 907 label_copy_source: Source
906 908 label_copy_target: Cible
907 909 label_copy_same_as_target: Comme la cible
908 910 label_display_used_statuses_only: N'afficher que les statuts utilisΓ©s dans ce tracker
909 911 label_api_access_key: Clé d'accès API
910 912 label_missing_api_access_key: Clé d'accès API manquante
911 913 label_api_access_key_created_on: Clé d'accès API créée il y a %{value}
912 914 label_profile: Profil
913 915 label_subtask_plural: Sous-tΓ’ches
914 916 label_project_copy_notifications: Envoyer les notifications durant la copie du projet
915 917 label_principal_search: "Rechercher un utilisateur ou un groupe :"
916 918 label_user_search: "Rechercher un utilisateur :"
917 919 label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande
918 920 label_additional_workflow_transitions_for_assignee: Autorisations supplΓ©mentaires lorsque la demande est assignΓ©e Γ  l'utilisateur
919 921 label_issues_visibility_all: Toutes les demandes
920 922 label_issues_visibility_public: Toutes les demandes non privΓ©es
921 923 label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur
922 924 label_git_report_last_commit: Afficher le dernier commit des fichiers et rΓ©pertoires
923 925 label_parent_revision: Parent
924 926 label_child_revision: Enfant
925 927 label_export_options: Options d'exportation %{export_format}
926 928 label_copy_attachments: Copier les fichiers
927 929 label_copy_subtasks: Copier les sous-tΓ’ches
928 930 label_item_position: "%{position} sur %{count}"
929 931 label_completed_versions: Versions passΓ©es
930 932 label_search_for_watchers: Rechercher des observateurs
931 933 label_session_expiration: Expiration des sessions
932 934 label_show_closed_projects: Voir les projets fermΓ©s
933 935 label_status_transitions: Changements de statut
934 936 label_fields_permissions: Permissions sur les champs
935 937 label_readonly: Lecture
936 938 label_required: Obligatoire
937 939 label_hidden: CachΓ©
938 940 label_attribute_of_project: "%{name} du projet"
939 941 label_attribute_of_issue: "%{name} de la demande"
940 942 label_attribute_of_author: "%{name} de l'auteur"
941 943 label_attribute_of_assigned_to: "%{name} de l'assignΓ©"
942 944 label_attribute_of_user: "%{name} de l'utilisateur"
943 945 label_attribute_of_fixed_version: "%{name} de la version cible"
944 946 label_cross_project_descendants: Avec les sous-projets
945 947 label_cross_project_tree: Avec tout l'arbre
946 948 label_cross_project_hierarchy: Avec toute la hiΓ©rarchie
947 949 label_cross_project_system: Avec tous les projets
948 950 label_gantt_progress_line: Ligne de progression
949 951 label_visibility_private: par moi uniquement
950 952 label_visibility_roles: par ces rΓ΄les uniquement
951 953 label_visibility_public: par tout le monde
952 954 label_link: Lien
953 955 label_only: seulement
954 956 label_drop_down_list: liste dΓ©roulante
955 957 label_checkboxes: cases Γ  cocher
956 958 label_radio_buttons: boutons radio
957 959 label_link_values_to: Lier les valeurs vers l'URL
958 960 label_custom_field_select_type: Selectionner le type d'objet auquel attacher le champ personnalisΓ©
959 961 label_check_for_updates: VΓ©rifier les mises Γ  jour
960 962 label_latest_compatible_version: Dernière version compatible
961 963 label_unknown_plugin: Plugin inconnu
962 964 label_add_projects: Ajouter des projets
963 965 label_users_visibility_all: Tous les utilisateurs actifs
964 966 label_users_visibility_members_of_visible_projects: Membres des projets visibles
965 967 label_edit_attachments: Modifier les fichiers attachΓ©s
966 968 label_link_copied_issue: Lier la demande copiΓ©e
967 969 label_ask: Demander
968 970 label_search_attachments_yes: Rechercher les noms et descriptions de fichiers
969 971 label_search_attachments_no: Ne pas rechercher les fichiers
970 972 label_search_attachments_only: Rechercher les fichiers uniquement
971 973 label_search_open_issues_only: Demandes ouvertes uniquement
972 974 label_email_address_plural: Emails
973 975 label_email_address_add: Ajouter une adresse email
974 976 label_enable_notifications: Activer les notifications
975 977 label_disable_notifications: DΓ©sactiver les notifications
976 978 label_blank_value: non renseignΓ©
977 979 label_parent_task_attributes: Attributs des tΓ’ches parentes
978 980 label_time_entries_visibility_all: Tous les temps passΓ©s
979 981 label_time_entries_visibility_own: Ses propres temps passΓ©s
980 982 label_member_management: Gestion des membres
981 983 label_member_management_all_roles: Tous les rΓ΄les
982 984 label_member_management_selected_roles_only: Ces rΓ΄les uniquement
983 985 label_import_issues: Importer des demandes
984 986 label_select_file_to_import: SΓ©lectionner le fichier Γ  importer
985 987 label_fields_separator: SΓ©parateur de champs
986 988 label_fields_wrapper: DΓ©limiteur de texte
987 989 label_encoding: Encodage
988 990 label_comma_char: Virgule
989 991 label_semi_colon_char: Point virgule
990 992 label_quote_char: Apostrophe
991 993 label_double_quote_char: Double apostrophe
992 994 label_fields_mapping: Correspondance des champs
993 995 label_file_content_preview: AperΓ§u du contenu du fichier
994 996 label_create_missing_values: CrΓ©er les valeurs manquantes
995 997 label_api: API
996 998 label_field_format_enumeration: Liste clΓ©/valeur
997 999
998 1000 button_login: Connexion
999 1001 button_submit: Soumettre
1000 1002 button_save: Sauvegarder
1001 1003 button_check_all: Tout cocher
1002 1004 button_uncheck_all: Tout dΓ©cocher
1003 1005 button_collapse_all: Plier tout
1004 1006 button_expand_all: DΓ©plier tout
1005 1007 button_delete: Supprimer
1006 1008 button_create: CrΓ©er
1007 1009 button_create_and_continue: CrΓ©er et continuer
1008 1010 button_test: Tester
1009 1011 button_edit: Modifier
1010 1012 button_edit_associated_wikipage: "Modifier la page wiki associΓ©e: %{page_title}"
1011 1013 button_add: Ajouter
1012 1014 button_change: Changer
1013 1015 button_apply: Appliquer
1014 1016 button_clear: Effacer
1015 1017 button_lock: Verrouiller
1016 1018 button_unlock: DΓ©verrouiller
1017 1019 button_download: TΓ©lΓ©charger
1018 1020 button_list: Lister
1019 1021 button_view: Voir
1020 1022 button_move: DΓ©placer
1021 1023 button_move_and_follow: DΓ©placer et suivre
1022 1024 button_back: Retour
1023 1025 button_cancel: Annuler
1024 1026 button_activate: Activer
1025 1027 button_sort: Trier
1026 1028 button_log_time: Saisir temps
1027 1029 button_rollback: Revenir Γ  cette version
1028 1030 button_watch: Surveiller
1029 1031 button_unwatch: Ne plus surveiller
1030 1032 button_reply: RΓ©pondre
1031 1033 button_archive: Archiver
1032 1034 button_unarchive: DΓ©sarchiver
1033 1035 button_reset: RΓ©initialiser
1034 1036 button_rename: Renommer
1035 1037 button_change_password: Changer de mot de passe
1036 1038 button_copy: Copier
1037 1039 button_copy_and_follow: Copier et suivre
1038 1040 button_annotate: Annoter
1039 1041 button_update: Mettre Γ  jour
1040 1042 button_configure: Configurer
1041 1043 button_quote: Citer
1042 1044 button_duplicate: Dupliquer
1043 1045 button_show: Afficher
1044 1046 button_hide: Cacher
1045 1047 button_edit_section: Modifier cette section
1046 1048 button_export: Exporter
1047 1049 button_delete_my_account: Supprimer mon compte
1048 1050 button_close: Fermer
1049 1051 button_reopen: RΓ©ouvrir
1050 1052 button_import: Importer
1051 1053
1052 1054 status_active: actif
1053 1055 status_registered: enregistrΓ©
1054 1056 status_locked: verrouillΓ©
1055 1057
1056 1058 project_status_active: actif
1057 1059 project_status_closed: fermΓ©
1058 1060 project_status_archived: archivΓ©
1059 1061
1060 1062 version_status_open: ouvert
1061 1063 version_status_locked: verrouillΓ©
1062 1064 version_status_closed: fermΓ©
1063 1065
1064 1066 field_active: Actif
1065 1067
1066 1068 text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e
1067 1069 text_regexp_info: ex. ^[A-Z0-9]+$
1068 1070 text_min_max_length_info: 0 pour aucune restriction
1069 1071 text_project_destroy_confirmation: Êtes-vous sûr de vouloir supprimer ce projet et toutes ses données ?
1070 1072 text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront Γ©galement supprimΓ©s."
1071 1073 text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow
1072 1074 text_are_you_sure: Êtes-vous sûr ?
1073 1075 text_journal_changed: "%{label} changΓ© de %{old} Γ  %{new}"
1074 1076 text_journal_changed_no_detail: "%{label} mis Γ  jour"
1075 1077 text_journal_set_to: "%{label} mis Γ  %{value}"
1076 1078 text_journal_deleted: "%{label} %{old} supprimΓ©"
1077 1079 text_journal_added: "%{label} %{value} ajoutΓ©"
1078 1080 text_tip_issue_begin_day: tΓ’che commenΓ§ant ce jour
1079 1081 text_tip_issue_end_day: tΓ’che finissant ce jour
1080 1082 text_tip_issue_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour
1081 1083 text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et tirets bas sont autorisΓ©s, doit commencer par une minuscule.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.'
1082 1084 text_caracters_maximum: "%{count} caractères maximum."
1083 1085 text_caracters_minimum: "%{count} caractères minimum."
1084 1086 text_length_between: "Longueur comprise entre %{min} et %{max} caractères."
1085 1087 text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker
1086 1088 text_unallowed_characters: Caractères non autorisés
1087 1089 text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules).
1088 1090 text_line_separated: Plusieurs valeurs possibles (une valeur par ligne).
1089 1091 text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits
1090 1092 text_issue_added: "La demande %{id} a Γ©tΓ© soumise par %{author}."
1091 1093 text_issue_updated: "La demande %{id} a Γ©tΓ© mise Γ  jour par %{author}."
1092 1094 text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ?
1093 1095 text_issue_category_destroy_question: "%{count} demandes sont affectΓ©es Γ  cette catΓ©gorie. Que voulez-vous faire ?"
1094 1096 text_issue_category_destroy_assignments: N'affecter les demandes Γ  aucune autre catΓ©gorie
1095 1097 text_issue_category_reassign_to: RΓ©affecter les demandes Γ  cette catΓ©gorie
1096 1098 text_user_mail_option: "Pour les projets non sΓ©lectionnΓ©s, vous recevrez seulement des notifications pour ce que vous surveillez ou Γ  quoi vous participez (exemple: demandes dont vous Γͺtes l'auteur ou la personne assignΓ©e)."
1097 1099 text_no_configuration_data: "Les rΓ΄les, trackers, statuts et le workflow ne sont pas encore paramΓ©trΓ©s.\nIl est vivement recommandΓ© de charger le paramΓ©trage par defaut. Vous pourrez le modifier une fois chargΓ©."
1098 1100 text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut
1099 1101 text_status_changed_by_changeset: "AppliquΓ© par commit %{value}."
1100 1102 text_time_logged_by_changeset: "AppliquΓ© par commit %{value}"
1101 1103 text_issues_destroy_confirmation: 'Êtes-vous sûr de vouloir supprimer la ou les demandes(s) selectionnée(s) ?'
1102 1104 text_issues_destroy_descendants_confirmation: "Cela entrainera Γ©galement la suppression de %{count} sous-tΓ’che(s)."
1103 1105 text_time_entries_destroy_confirmation: "Etes-vous sΓ»r de vouloir supprimer les temps passΓ©s sΓ©lectionnΓ©s ?"
1104 1106 text_select_project_modules: 'SΓ©lectionner les modules Γ  activer pour ce projet :'
1105 1107 text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ©
1106 1108 text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture
1107 1109 text_plugin_assets_writable: RΓ©pertoire public des plugins accessible en Γ©criture
1108 1110 text_rmagick_available: Bibliothèque RMagick présente (optionnelle)
1109 1111 text_convert_available: Binaire convert de ImageMagick prΓ©sent (optionel)
1110 1112 text_destroy_time_entries_question: "%{hours} heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ  supprimer. Que voulez-vous faire ?"
1111 1113 text_destroy_time_entries: Supprimer les heures
1112 1114 text_assign_time_entries_to_project: Reporter les heures sur le projet
1113 1115 text_reassign_time_entries: 'Reporter les heures sur cette demande:'
1114 1116 text_user_wrote: "%{value} a Γ©crit :"
1115 1117 text_enumeration_destroy_question: "La valeur Β« %{name} Β» est affectΓ©e Γ  %{count} objet(s)."
1116 1118 text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ  cette valeur:'
1117 1119 text_email_delivery_not_configured: "L'envoi de mail n'est pas configurΓ©, les notifications sont dΓ©sactivΓ©es.\nConfigurez votre serveur SMTP dans config/configuration.yml et redΓ©marrez l'application pour les activer."
1118 1120 text_repository_usernames_mapping: "Vous pouvez sΓ©lectionner ou modifier l'utilisateur Redmine associΓ© Γ  chaque nom d'utilisateur figurant dans l'historique du dΓ©pΓ΄t.\nLes utilisateurs avec le mΓͺme identifiant ou la mΓͺme adresse mail seront automatiquement associΓ©s."
1119 1121 text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.'
1120 1122 text_custom_field_possible_values_info: 'Une ligne par valeur'
1121 1123 text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?"
1122 1124 text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines"
1123 1125 text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes"
1124 1126 text_wiki_page_reassign_children: "RΓ©affecter les sous-pages Γ  cette page"
1125 1127 text_own_membership_delete_confirmation: "Vous allez supprimer tout ou partie de vos permissions sur ce projet et ne serez peut-Γͺtre plus autorisΓ© Γ  modifier ce projet.\nEtes-vous sΓ»r de vouloir continuer ?"
1126 1128 text_zoom_in: Zoom avant
1127 1129 text_zoom_out: Zoom arrière
1128 1130 text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardΓ© qui sera perdu si vous quittez la page."
1129 1131 text_scm_path_encoding_note: "DΓ©faut : UTF-8"
1130 1132 text_subversion_repository_note: "Exemples (en fonction des protocoles supportΓ©s) : file:///, http://, https://, svn://, svn+[tunnelscheme]://"
1131 1133 text_git_repository_note: "Chemin vers un dΓ©pΓ΄t vide et local (exemples : /gitrepo, c:\\gitrepo)"
1132 1134 text_mercurial_repository_note: "Chemin vers un dΓ©pΓ΄t local (exemples : /hgrepo, c:\\hgrepo)"
1133 1135 text_scm_command: Commande
1134 1136 text_scm_command_version: Version
1135 1137 text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification.
1136 1138 text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration.
1137 1139 text_issue_conflict_resolution_overwrite: "Appliquer quand mΓͺme ma mise Γ  jour (les notes prΓ©cΓ©dentes seront conservΓ©es mais des changements pourront Γͺtre Γ©crasΓ©s)"
1138 1140 text_issue_conflict_resolution_add_notes: "Ajouter mes notes et ignorer mes autres changements"
1139 1141 text_issue_conflict_resolution_cancel: "Annuler ma mise Γ  jour et rΓ©afficher %{link}"
1140 1142 text_account_destroy_confirmation: "Êtes-vous sûr de vouloir continuer ?\nVotre compte sera définitivement supprimé, sans aucune possibilité de le réactiver."
1141 1143 text_session_expiration_settings: "Attention : le changement de ces paramètres peut entrainer l'expiration des sessions utilisateurs en cours, y compris la vôtre."
1142 1144 text_project_closed: Ce projet est fermΓ© et accessible en lecture seule.
1143 1145 text_turning_multiple_off: "Si vous dΓ©sactivez les valeurs multiples, les valeurs multiples seront supprimΓ©es pour n'en conserver qu'une par objet."
1144 1146
1145 1147 default_role_manager: Manager
1146 1148 default_role_developer: DΓ©veloppeur
1147 1149 default_role_reporter: Rapporteur
1148 1150 default_tracker_bug: Anomalie
1149 1151 default_tracker_feature: Evolution
1150 1152 default_tracker_support: Assistance
1151 1153 default_issue_status_new: Nouveau
1152 1154 default_issue_status_in_progress: En cours
1153 1155 default_issue_status_resolved: RΓ©solu
1154 1156 default_issue_status_feedback: Commentaire
1155 1157 default_issue_status_closed: FermΓ©
1156 1158 default_issue_status_rejected: RejetΓ©
1157 1159 default_doc_category_user: Documentation utilisateur
1158 1160 default_doc_category_tech: Documentation technique
1159 1161 default_priority_low: Bas
1160 1162 default_priority_normal: Normal
1161 1163 default_priority_high: Haut
1162 1164 default_priority_urgent: Urgent
1163 1165 default_priority_immediate: ImmΓ©diat
1164 1166 default_activity_design: Conception
1165 1167 default_activity_development: DΓ©veloppement
1166 1168
1167 1169 enumeration_issue_priorities: PrioritΓ©s des demandes
1168 1170 enumeration_doc_categories: CatΓ©gories des documents
1169 1171 enumeration_activities: ActivitΓ©s (suivi du temps)
1170 1172 enumeration_system_activity: Activité système
1171 1173 description_filter: Filtre
1172 1174 description_search: Champ de recherche
1173 1175 description_choose_project: Projets
1174 1176 description_project_scope: Périmètre de recherche
1175 1177 description_notes: Notes
1176 1178 description_message_content: Contenu du message
1177 1179 description_query_sort_criteria_attribute: Critère de tri
1178 1180 description_query_sort_criteria_direction: Ordre de tri
1179 1181 description_user_mail_notification: Option de notification
1180 1182 description_available_columns: Colonnes disponibles
1181 1183 description_selected_columns: Colonnes sΓ©lectionnΓ©es
1182 1184 description_all_columns: Toutes les colonnes
1183 1185 description_issue_category_reassign: Choisir une catΓ©gorie
1184 1186 description_wiki_subpages_reassign: Choisir une nouvelle page parent
1185 1187 description_date_range_list: Choisir une pΓ©riode prΓ©dΓ©finie
1186 1188 description_date_range_interval: Choisir une pΓ©riode
1187 1189 description_date_from: Date de dΓ©but
1188 1190 description_date_to: Date de fin
1189 1191 text_repository_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et tirets bas sont autorisΓ©s.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.'
1190 1192 label_parent_task_attributes_derived: Calculated from subtasks
1191 1193 label_parent_task_attributes_independent: Independent of subtasks
@@ -1,671 +1,673
1 1 /* Redmine - project management software
2 2 Copyright (C) 2006-2015 Jean-Philippe Lang */
3 3
4 4 function checkAll(id, checked) {
5 5 $('#'+id).find('input[type=checkbox]:enabled').prop('checked', checked);
6 6 }
7 7
8 8 function toggleCheckboxesBySelector(selector) {
9 9 var all_checked = true;
10 10 $(selector).each(function(index) {
11 11 if (!$(this).is(':checked')) { all_checked = false; }
12 12 });
13 13 $(selector).prop('checked', !all_checked);
14 14 }
15 15
16 16 function showAndScrollTo(id, focus) {
17 17 $('#'+id).show();
18 18 if (focus !== null) {
19 19 $('#'+focus).focus();
20 20 }
21 21 $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
22 22 }
23 23
24 24 function toggleRowGroup(el) {
25 25 var tr = $(el).parents('tr').first();
26 26 var n = tr.next();
27 27 tr.toggleClass('open');
28 28 while (n.length && !n.hasClass('group')) {
29 29 n.toggle();
30 30 n = n.next('tr');
31 31 }
32 32 }
33 33
34 34 function collapseAllRowGroups(el) {
35 35 var tbody = $(el).parents('tbody').first();
36 36 tbody.children('tr').each(function(index) {
37 37 if ($(this).hasClass('group')) {
38 38 $(this).removeClass('open');
39 39 } else {
40 40 $(this).hide();
41 41 }
42 42 });
43 43 }
44 44
45 45 function expandAllRowGroups(el) {
46 46 var tbody = $(el).parents('tbody').first();
47 47 tbody.children('tr').each(function(index) {
48 48 if ($(this).hasClass('group')) {
49 49 $(this).addClass('open');
50 50 } else {
51 51 $(this).show();
52 52 }
53 53 });
54 54 }
55 55
56 56 function toggleAllRowGroups(el) {
57 57 var tr = $(el).parents('tr').first();
58 58 if (tr.hasClass('open')) {
59 59 collapseAllRowGroups(el);
60 60 } else {
61 61 expandAllRowGroups(el);
62 62 }
63 63 }
64 64
65 65 function toggleFieldset(el) {
66 66 var fieldset = $(el).parents('fieldset').first();
67 67 fieldset.toggleClass('collapsed');
68 68 fieldset.children('div').toggle();
69 69 }
70 70
71 71 function hideFieldset(el) {
72 72 var fieldset = $(el).parents('fieldset').first();
73 73 fieldset.toggleClass('collapsed');
74 74 fieldset.children('div').hide();
75 75 }
76 76
77 77 // columns selection
78 78 function moveOptions(theSelFrom, theSelTo) {
79 79 $(theSelFrom).find('option:selected').detach().prop("selected", false).appendTo($(theSelTo));
80 80 }
81 81
82 82 function moveOptionUp(theSel) {
83 83 $(theSel).find('option:selected').each(function(){
84 84 $(this).prev(':not(:selected)').detach().insertAfter($(this));
85 85 });
86 86 }
87 87
88 88 function moveOptionTop(theSel) {
89 89 $(theSel).find('option:selected').detach().prependTo($(theSel));
90 90 }
91 91
92 92 function moveOptionDown(theSel) {
93 93 $($(theSel).find('option:selected').get().reverse()).each(function(){
94 94 $(this).next(':not(:selected)').detach().insertBefore($(this));
95 95 });
96 96 }
97 97
98 98 function moveOptionBottom(theSel) {
99 99 $(theSel).find('option:selected').detach().appendTo($(theSel));
100 100 }
101 101
102 102 function initFilters() {
103 103 $('#add_filter_select').change(function() {
104 104 addFilter($(this).val(), '', []);
105 105 });
106 106 $('#filters-table td.field input[type=checkbox]').each(function() {
107 107 toggleFilter($(this).val());
108 108 });
109 109 $('#filters-table').on('click', 'td.field input[type=checkbox]', function() {
110 110 toggleFilter($(this).val());
111 111 });
112 112 $('#filters-table').on('click', '.toggle-multiselect', function() {
113 113 toggleMultiSelect($(this).siblings('select'));
114 114 });
115 115 $('#filters-table').on('keypress', 'input[type=text]', function(e) {
116 116 if (e.keyCode == 13) $(this).closest('form').submit();
117 117 });
118 118 }
119 119
120 120 function addFilter(field, operator, values) {
121 121 var fieldId = field.replace('.', '_');
122 122 var tr = $('#tr_'+fieldId);
123 123 if (tr.length > 0) {
124 124 tr.show();
125 125 } else {
126 126 buildFilterRow(field, operator, values);
127 127 }
128 128 $('#cb_'+fieldId).prop('checked', true);
129 129 toggleFilter(field);
130 130 $('#add_filter_select').val('').find('option').each(function() {
131 131 if ($(this).attr('value') == field) {
132 132 $(this).attr('disabled', true);
133 133 }
134 134 });
135 135 }
136 136
137 137 function buildFilterRow(field, operator, values) {
138 138 var fieldId = field.replace('.', '_');
139 139 var filterTable = $("#filters-table");
140 140 var filterOptions = availableFilters[field];
141 141 if (!filterOptions) return;
142 142 var operators = operatorByType[filterOptions['type']];
143 143 var filterValues = filterOptions['values'];
144 144 var i, select;
145 145
146 146 var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html(
147 147 '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' +
148 148 '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' +
149 149 '<td class="values"></td>'
150 150 );
151 151 filterTable.append(tr);
152 152
153 153 select = tr.find('td.operator select');
154 154 for (i = 0; i < operators.length; i++) {
155 155 var option = $('<option>').val(operators[i]).text(operatorLabels[operators[i]]);
156 156 if (operators[i] == operator) { option.attr('selected', true); }
157 157 select.append(option);
158 158 }
159 159 select.change(function(){ toggleOperator(field); });
160 160
161 161 switch (filterOptions['type']) {
162 162 case "list":
163 163 case "list_optional":
164 164 case "list_status":
165 165 case "list_subprojects":
166 166 tr.find('td.values').append(
167 167 '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
168 168 ' <span class="toggle-multiselect">&nbsp;</span></span>'
169 169 );
170 170 select = tr.find('td.values select');
171 171 if (values.length > 1) { select.attr('multiple', true); }
172 172 for (i = 0; i < filterValues.length; i++) {
173 173 var filterValue = filterValues[i];
174 174 var option = $('<option>');
175 175 if ($.isArray(filterValue)) {
176 176 option.val(filterValue[1]).text(filterValue[0]);
177 177 if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);}
178 178 } else {
179 179 option.val(filterValue).text(filterValue);
180 180 if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);}
181 181 }
182 182 select.append(option);
183 183 }
184 184 break;
185 185 case "date":
186 186 case "date_past":
187 187 tr.find('td.values').append(
188 188 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' +
189 189 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' +
190 190 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>'
191 191 );
192 192 $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions);
193 193 $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions);
194 194 $('#values_'+fieldId).val(values[0]);
195 195 break;
196 196 case "string":
197 197 case "text":
198 198 tr.find('td.values').append(
199 199 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>'
200 200 );
201 201 $('#values_'+fieldId).val(values[0]);
202 202 break;
203 203 case "relation":
204 204 tr.find('td.values').append(
205 205 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="6" class="value" /></span>' +
206 206 '<span style="display:none;"><select class="value" name="v['+field+'][]" id="values_'+fieldId+'_1"></select></span>'
207 207 );
208 208 $('#values_'+fieldId).val(values[0]);
209 209 select = tr.find('td.values select');
210 210 for (i = 0; i < allProjects.length; i++) {
211 211 var filterValue = allProjects[i];
212 212 var option = $('<option>');
213 213 option.val(filterValue[1]).text(filterValue[0]);
214 214 if (values[0] == filterValue[1]) { option.attr('selected', true); }
215 215 select.append(option);
216 216 }
217 217 break;
218 218 case "integer":
219 219 case "float":
220 220 case "tree":
221 221 tr.find('td.values').append(
222 222 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" /></span>' +
223 223 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" /></span>'
224 224 );
225 225 $('#values_'+fieldId+'_1').val(values[0]);
226 226 $('#values_'+fieldId+'_2').val(values[1]);
227 227 break;
228 228 }
229 229 }
230 230
231 231 function toggleFilter(field) {
232 232 var fieldId = field.replace('.', '_');
233 233 if ($('#cb_' + fieldId).is(':checked')) {
234 234 $("#operators_" + fieldId).show().removeAttr('disabled');
235 235 toggleOperator(field);
236 236 } else {
237 237 $("#operators_" + fieldId).hide().attr('disabled', true);
238 238 enableValues(field, []);
239 239 }
240 240 }
241 241
242 242 function enableValues(field, indexes) {
243 243 var fieldId = field.replace('.', '_');
244 244 $('#tr_'+fieldId+' td.values .value').each(function(index) {
245 245 if ($.inArray(index, indexes) >= 0) {
246 246 $(this).removeAttr('disabled');
247 247 $(this).parents('span').first().show();
248 248 } else {
249 249 $(this).val('');
250 250 $(this).attr('disabled', true);
251 251 $(this).parents('span').first().hide();
252 252 }
253 253
254 254 if ($(this).hasClass('group')) {
255 255 $(this).addClass('open');
256 256 } else {
257 257 $(this).show();
258 258 }
259 259 });
260 260 }
261 261
262 262 function toggleOperator(field) {
263 263 var fieldId = field.replace('.', '_');
264 264 var operator = $("#operators_" + fieldId);
265 265 switch (operator.val()) {
266 266 case "!*":
267 267 case "*":
268 268 case "t":
269 269 case "ld":
270 270 case "w":
271 271 case "lw":
272 272 case "l2w":
273 273 case "m":
274 274 case "lm":
275 275 case "y":
276 276 case "o":
277 277 case "c":
278 case "*o":
279 case "!o":
278 280 enableValues(field, []);
279 281 break;
280 282 case "><":
281 283 enableValues(field, [0,1]);
282 284 break;
283 285 case "<t+":
284 286 case ">t+":
285 287 case "><t+":
286 288 case "t+":
287 289 case ">t-":
288 290 case "<t-":
289 291 case "><t-":
290 292 case "t-":
291 293 enableValues(field, [2]);
292 294 break;
293 295 case "=p":
294 296 case "=!p":
295 297 case "!p":
296 298 enableValues(field, [1]);
297 299 break;
298 300 default:
299 301 enableValues(field, [0]);
300 302 break;
301 303 }
302 304 }
303 305
304 306 function toggleMultiSelect(el) {
305 307 if (el.attr('multiple')) {
306 308 el.removeAttr('multiple');
307 309 el.attr('size', 1);
308 310 } else {
309 311 el.attr('multiple', true);
310 312 if (el.children().length > 10)
311 313 el.attr('size', 10);
312 314 else
313 315 el.attr('size', 4);
314 316 }
315 317 }
316 318
317 319 function showTab(name, url) {
318 320 $('#tab-content-' + name).parent().find('.tab-content').hide();
319 321 $('#tab-content-' + name).parent().find('div.tabs a').removeClass('selected');
320 322 $('#tab-content-' + name).show();
321 323 $('#tab-' + name).addClass('selected');
322 324 //replaces current URL with the "href" attribute of the current link
323 325 //(only triggered if supported by browser)
324 326 if ("replaceState" in window.history) {
325 327 window.history.replaceState(null, document.title, url);
326 328 }
327 329 return false;
328 330 }
329 331
330 332 function moveTabRight(el) {
331 333 var lis = $(el).parents('div.tabs').first().find('ul').children();
332 334 var tabsWidth = 0;
333 335 var i = 0;
334 336 lis.each(function() {
335 337 if ($(this).is(':visible')) {
336 338 tabsWidth += $(this).width() + 6;
337 339 }
338 340 });
339 341 if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; }
340 342 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
341 343 lis.eq(i).hide();
342 344 }
343 345
344 346 function moveTabLeft(el) {
345 347 var lis = $(el).parents('div.tabs').first().find('ul').children();
346 348 var i = 0;
347 349 while (i < lis.length && !lis.eq(i).is(':visible')) { i++; }
348 350 if (i > 0) {
349 351 lis.eq(i-1).show();
350 352 }
351 353 }
352 354
353 355 function displayTabsButtons() {
354 356 var lis;
355 357 var tabsWidth;
356 358 var el;
357 359 $('div.tabs').each(function() {
358 360 el = $(this);
359 361 lis = el.find('ul').children();
360 362 tabsWidth = 0;
361 363 lis.each(function(){
362 364 if ($(this).is(':visible')) {
363 365 tabsWidth += $(this).width() + 6;
364 366 }
365 367 });
366 368 if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) {
367 369 el.find('div.tabs-buttons').hide();
368 370 } else {
369 371 el.find('div.tabs-buttons').show();
370 372 }
371 373 });
372 374 }
373 375
374 376 function setPredecessorFieldsVisibility() {
375 377 var relationType = $('#relation_relation_type');
376 378 if (relationType.val() == "precedes" || relationType.val() == "follows") {
377 379 $('#predecessor_fields').show();
378 380 } else {
379 381 $('#predecessor_fields').hide();
380 382 }
381 383 }
382 384
383 385 function showModal(id, width, title) {
384 386 var el = $('#'+id).first();
385 387 if (el.length === 0 || el.is(':visible')) {return;}
386 388 if (!title) title = el.find('h3.title').text();
387 389 // moves existing modals behind the transparent background
388 390 $(".modal").zIndex(99);
389 391 el.dialog({
390 392 width: width,
391 393 modal: true,
392 394 resizable: false,
393 395 dialogClass: 'modal',
394 396 title: title
395 397 }).on('dialogclose', function(){
396 398 $(".modal").zIndex(101);
397 399 });
398 400 el.find("input[type=text], input[type=submit]").first().focus();
399 401 }
400 402
401 403 function hideModal(el) {
402 404 var modal;
403 405 if (el) {
404 406 modal = $(el).parents('.ui-dialog-content');
405 407 } else {
406 408 modal = $('#ajax-modal');
407 409 }
408 410 modal.dialog("close");
409 411 }
410 412
411 413 function submitPreview(url, form, target) {
412 414 $.ajax({
413 415 url: url,
414 416 type: 'post',
415 417 data: $('#'+form).serialize(),
416 418 success: function(data){
417 419 $('#'+target).html(data);
418 420 }
419 421 });
420 422 }
421 423
422 424 function collapseScmEntry(id) {
423 425 $('.'+id).each(function() {
424 426 if ($(this).hasClass('open')) {
425 427 collapseScmEntry($(this).attr('id'));
426 428 }
427 429 $(this).hide();
428 430 });
429 431 $('#'+id).removeClass('open');
430 432 }
431 433
432 434 function expandScmEntry(id) {
433 435 $('.'+id).each(function() {
434 436 $(this).show();
435 437 if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
436 438 expandScmEntry($(this).attr('id'));
437 439 }
438 440 });
439 441 $('#'+id).addClass('open');
440 442 }
441 443
442 444 function scmEntryClick(id, url) {
443 445 var el = $('#'+id);
444 446 if (el.hasClass('open')) {
445 447 collapseScmEntry(id);
446 448 el.addClass('collapsed');
447 449 return false;
448 450 } else if (el.hasClass('loaded')) {
449 451 expandScmEntry(id);
450 452 el.removeClass('collapsed');
451 453 return false;
452 454 }
453 455 if (el.hasClass('loading')) {
454 456 return false;
455 457 }
456 458 el.addClass('loading');
457 459 $.ajax({
458 460 url: url,
459 461 success: function(data) {
460 462 el.after(data);
461 463 el.addClass('open').addClass('loaded').removeClass('loading');
462 464 }
463 465 });
464 466 return true;
465 467 }
466 468
467 469 function randomKey(size) {
468 470 var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
469 471 var key = '';
470 472 for (var i = 0; i < size; i++) {
471 473 key += chars.charAt(Math.floor(Math.random() * chars.length));
472 474 }
473 475 return key;
474 476 }
475 477
476 478 function updateIssueFrom(url, el) {
477 479 $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
478 480 $(this).data('valuebeforeupdate', $(this).val());
479 481 });
480 482 if (el) {
481 483 $("#form_update_triggered_by").val($(el).attr('id'));
482 484 }
483 485 return $.ajax({
484 486 url: url,
485 487 type: 'post',
486 488 data: $('#issue-form').serialize()
487 489 });
488 490 }
489 491
490 492 function replaceIssueFormWith(html){
491 493 var replacement = $(html);
492 494 $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
493 495 var object_id = $(this).attr('id');
494 496 if (object_id && $(this).data('valuebeforeupdate')!=$(this).val()) {
495 497 replacement.find('#'+object_id).val($(this).val());
496 498 }
497 499 });
498 500 $('#all_attributes').empty();
499 501 $('#all_attributes').prepend(replacement);
500 502 }
501 503
502 504 function updateBulkEditFrom(url) {
503 505 $.ajax({
504 506 url: url,
505 507 type: 'post',
506 508 data: $('#bulk_edit_form').serialize()
507 509 });
508 510 }
509 511
510 512 function observeAutocompleteField(fieldId, url, options) {
511 513 $(document).ready(function() {
512 514 $('#'+fieldId).autocomplete($.extend({
513 515 source: url,
514 516 minLength: 2,
515 517 search: function(){$('#'+fieldId).addClass('ajax-loading');},
516 518 response: function(){$('#'+fieldId).removeClass('ajax-loading');}
517 519 }, options));
518 520 $('#'+fieldId).addClass('autocomplete');
519 521 });
520 522 }
521 523
522 524 function observeSearchfield(fieldId, targetId, url) {
523 525 $('#'+fieldId).each(function() {
524 526 var $this = $(this);
525 527 $this.addClass('autocomplete');
526 528 $this.attr('data-value-was', $this.val());
527 529 var check = function() {
528 530 var val = $this.val();
529 531 if ($this.attr('data-value-was') != val){
530 532 $this.attr('data-value-was', val);
531 533 $.ajax({
532 534 url: url,
533 535 type: 'get',
534 536 data: {q: $this.val()},
535 537 success: function(data){ if(targetId) $('#'+targetId).html(data); },
536 538 beforeSend: function(){ $this.addClass('ajax-loading'); },
537 539 complete: function(){ $this.removeClass('ajax-loading'); }
538 540 });
539 541 }
540 542 };
541 543 var reset = function() {
542 544 if (timer) {
543 545 clearInterval(timer);
544 546 timer = setInterval(check, 300);
545 547 }
546 548 };
547 549 var timer = setInterval(check, 300);
548 550 $this.bind('keyup click mousemove', reset);
549 551 });
550 552 }
551 553
552 554 function beforeShowDatePicker(input, inst) {
553 555 var default_date = null;
554 556 switch ($(input).attr("id")) {
555 557 case "issue_start_date" :
556 558 if ($("#issue_due_date").size() > 0) {
557 559 default_date = $("#issue_due_date").val();
558 560 }
559 561 break;
560 562 case "issue_due_date" :
561 563 if ($("#issue_start_date").size() > 0) {
562 564 default_date = $("#issue_start_date").val();
563 565 }
564 566 break;
565 567 }
566 568 $(input).datepicker("option", "defaultDate", default_date);
567 569 }
568 570
569 571 function initMyPageSortable(list, url) {
570 572 $('#list-'+list).sortable({
571 573 connectWith: '.block-receiver',
572 574 tolerance: 'pointer',
573 575 update: function(){
574 576 $.ajax({
575 577 url: url,
576 578 type: 'post',
577 579 data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})}
578 580 });
579 581 }
580 582 });
581 583 $("#list-top, #list-left, #list-right").disableSelection();
582 584 }
583 585
584 586 var warnLeavingUnsavedMessage;
585 587 function warnLeavingUnsaved(message) {
586 588 warnLeavingUnsavedMessage = message;
587 589 $(document).on('submit', 'form', function(){
588 590 $('textarea').removeData('changed');
589 591 });
590 592 $(document).on('change', 'textarea', function(){
591 593 $(this).data('changed', 'changed');
592 594 });
593 595 window.onbeforeunload = function(){
594 596 var warn = false;
595 597 $('textarea').blur().each(function(){
596 598 if ($(this).data('changed')) {
597 599 warn = true;
598 600 }
599 601 });
600 602 if (warn) {return warnLeavingUnsavedMessage;}
601 603 };
602 604 }
603 605
604 606 function setupAjaxIndicator() {
605 607 $(document).bind('ajaxSend', function(event, xhr, settings) {
606 608 if ($('.ajax-loading').length === 0 && settings.contentType != 'application/octet-stream') {
607 609 $('#ajax-indicator').show();
608 610 }
609 611 });
610 612 $(document).bind('ajaxStop', function() {
611 613 $('#ajax-indicator').hide();
612 614 });
613 615 }
614 616
615 617 function hideOnLoad() {
616 618 $('.hol').hide();
617 619 }
618 620
619 621 function addFormObserversForDoubleSubmit() {
620 622 $('form[method=post]').each(function() {
621 623 if (!$(this).hasClass('multiple-submit')) {
622 624 $(this).submit(function(form_submission) {
623 625 if ($(form_submission.target).attr('data-submitted')) {
624 626 form_submission.preventDefault();
625 627 } else {
626 628 $(form_submission.target).attr('data-submitted', true);
627 629 }
628 630 });
629 631 }
630 632 });
631 633 }
632 634
633 635 function defaultFocus(){
634 636 if (($('#content :focus').length == 0) && (window.location.hash == '')) {
635 637 $('#content input[type=text], #content textarea').first().focus();
636 638 }
637 639 }
638 640
639 641 function blockEventPropagation(event) {
640 642 event.stopPropagation();
641 643 event.preventDefault();
642 644 }
643 645
644 646 function toggleDisabledOnChange() {
645 647 var checked = $(this).is(':checked');
646 648 $($(this).data('disables')).attr('disabled', checked);
647 649 $($(this).data('enables')).attr('disabled', !checked);
648 650 }
649 651 function toggleDisabledInit() {
650 652 $('input[data-disables], input[data-enables]').each(toggleDisabledOnChange);
651 653 }
652 654 $(document).ready(function(){
653 655 $('#content').on('change', 'input[data-disables], input[data-enables]', toggleDisabledOnChange);
654 656 toggleDisabledInit();
655 657 });
656 658
657 659 function keepAnchorOnSignIn(form){
658 660 var hash = decodeURIComponent(self.document.location.hash);
659 661 if (hash) {
660 662 if (hash.indexOf("#") === -1) {
661 663 hash = "#" + hash;
662 664 }
663 665 form.action = form.action + hash;
664 666 }
665 667 return true;
666 668 }
667 669
668 670 $(document).ready(setupAjaxIndicator);
669 671 $(document).ready(hideOnLoad);
670 672 $(document).ready(addFormObserversForDoubleSubmit);
671 673 $(document).ready(defaultFocus);
@@ -1,1671 +1,1699
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class QueryTest < ActiveSupport::TestCase
21 21 include Redmine::I18n
22 22
23 23 fixtures :projects, :enabled_modules, :users, :members,
24 24 :member_roles, :roles, :trackers, :issue_statuses,
25 25 :issue_categories, :enumerations, :issues,
26 26 :watchers, :custom_fields, :custom_values, :versions,
27 27 :queries,
28 28 :projects_trackers,
29 29 :custom_fields_trackers,
30 30 :workflows
31 31
32 32 def setup
33 33 User.current = nil
34 34 end
35 35
36 36 def test_query_with_roles_visibility_should_validate_roles
37 37 set_language_if_valid 'en'
38 38 query = IssueQuery.new(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES)
39 39 assert !query.save
40 40 assert_include "Roles cannot be blank", query.errors.full_messages
41 41 query.role_ids = [1, 2]
42 42 assert query.save
43 43 end
44 44
45 45 def test_changing_roles_visibility_should_clear_roles
46 46 query = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1, 2])
47 47 assert_equal 2, query.roles.count
48 48
49 49 query.visibility = IssueQuery::VISIBILITY_PUBLIC
50 50 query.save!
51 51 assert_equal 0, query.roles.count
52 52 end
53 53
54 54 def test_available_filters_should_be_ordered
55 55 set_language_if_valid 'en'
56 56 query = IssueQuery.new
57 57 assert_equal 0, query.available_filters.keys.index('status_id')
58 58 expected_order = [
59 59 "Status",
60 60 "Project",
61 61 "Tracker",
62 62 "Priority"
63 63 ]
64 64 assert_equal expected_order,
65 65 (query.available_filters.values.map{|v| v[:name]} & expected_order)
66 66 end
67 67
68 68 def test_available_filters_with_custom_fields_should_be_ordered
69 69 set_language_if_valid 'en'
70 70 UserCustomField.create!(
71 71 :name => 'order test', :field_format => 'string',
72 72 :is_for_all => true, :is_filter => true
73 73 )
74 74 query = IssueQuery.new
75 75 expected_order = [
76 76 "Searchable field",
77 77 "Database",
78 78 "Project's Development status",
79 79 "Author's order test",
80 80 "Assignee's order test"
81 81 ]
82 82 assert_equal expected_order,
83 83 (query.available_filters.values.map{|v| v[:name]} & expected_order)
84 84 end
85 85
86 86 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
87 87 query = IssueQuery.new(:project => nil, :name => '_')
88 88 assert query.available_filters.has_key?('cf_1')
89 89 assert !query.available_filters.has_key?('cf_3')
90 90 end
91 91
92 92 def test_system_shared_versions_should_be_available_in_global_queries
93 93 Version.find(2).update_attribute :sharing, 'system'
94 94 query = IssueQuery.new(:project => nil, :name => '_')
95 95 assert query.available_filters.has_key?('fixed_version_id')
96 96 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
97 97 end
98 98
99 99 def test_project_filter_in_global_queries
100 100 query = IssueQuery.new(:project => nil, :name => '_')
101 101 project_filter = query.available_filters["project_id"]
102 102 assert_not_nil project_filter
103 103 project_ids = project_filter[:values].map{|p| p[1]}
104 104 assert project_ids.include?("1") #public project
105 105 assert !project_ids.include?("2") #private project user cannot see
106 106 end
107 107
108 108 def test_available_filters_should_not_include_fields_disabled_on_all_trackers
109 109 Tracker.all.each do |tracker|
110 110 tracker.core_fields = Tracker::CORE_FIELDS - ['start_date']
111 111 tracker.save!
112 112 end
113 113
114 114 query = IssueQuery.new(:name => '_')
115 115 assert_include 'due_date', query.available_filters
116 116 assert_not_include 'start_date', query.available_filters
117 117 end
118 118
119 119 def find_issues_with_query(query)
120 120 Issue.joins(:status, :tracker, :project, :priority).where(
121 121 query.statement
122 122 ).to_a
123 123 end
124 124
125 125 def assert_find_issues_with_query_is_successful(query)
126 126 assert_nothing_raised do
127 127 find_issues_with_query(query)
128 128 end
129 129 end
130 130
131 131 def assert_query_statement_includes(query, condition)
132 132 assert_include condition, query.statement
133 133 end
134 134
135 135 def assert_query_result(expected, query)
136 136 assert_nothing_raised do
137 137 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
138 138 assert_equal expected.size, query.issue_count
139 139 end
140 140 end
141 141
142 142 def test_query_should_allow_shared_versions_for_a_project_query
143 143 subproject_version = Version.find(4)
144 144 query = IssueQuery.new(:project => Project.find(1), :name => '_')
145 145 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
146 146
147 147 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
148 148 end
149 149
150 150 def test_query_with_multiple_custom_fields
151 151 query = IssueQuery.find(1)
152 152 assert query.valid?
153 153 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
154 154 issues = find_issues_with_query(query)
155 155 assert_equal 1, issues.length
156 156 assert_equal Issue.find(3), issues.first
157 157 end
158 158
159 159 def test_operator_none
160 160 query = IssueQuery.new(:project => Project.find(1), :name => '_')
161 161 query.add_filter('fixed_version_id', '!*', [''])
162 162 query.add_filter('cf_1', '!*', [''])
163 163 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
164 164 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
165 165 find_issues_with_query(query)
166 166 end
167 167
168 168 def test_operator_none_for_integer
169 169 query = IssueQuery.new(:project => Project.find(1), :name => '_')
170 170 query.add_filter('estimated_hours', '!*', [''])
171 171 issues = find_issues_with_query(query)
172 172 assert !issues.empty?
173 173 assert issues.all? {|i| !i.estimated_hours}
174 174 end
175 175
176 176 def test_operator_none_for_date
177 177 query = IssueQuery.new(:project => Project.find(1), :name => '_')
178 178 query.add_filter('start_date', '!*', [''])
179 179 issues = find_issues_with_query(query)
180 180 assert !issues.empty?
181 181 assert issues.all? {|i| i.start_date.nil?}
182 182 end
183 183
184 184 def test_operator_none_for_string_custom_field
185 185 CustomField.find(2).update_attribute :default_value, ""
186 186 query = IssueQuery.new(:project => Project.find(1), :name => '_')
187 187 query.add_filter('cf_2', '!*', [''])
188 188 assert query.has_filter?('cf_2')
189 189 issues = find_issues_with_query(query)
190 190 assert !issues.empty?
191 191 assert issues.all? {|i| i.custom_field_value(2).blank?}
192 192 end
193 193
194 194 def test_operator_all
195 195 query = IssueQuery.new(:project => Project.find(1), :name => '_')
196 196 query.add_filter('fixed_version_id', '*', [''])
197 197 query.add_filter('cf_1', '*', [''])
198 198 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
199 199 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
200 200 find_issues_with_query(query)
201 201 end
202 202
203 203 def test_operator_all_for_date
204 204 query = IssueQuery.new(:project => Project.find(1), :name => '_')
205 205 query.add_filter('start_date', '*', [''])
206 206 issues = find_issues_with_query(query)
207 207 assert !issues.empty?
208 208 assert issues.all? {|i| i.start_date.present?}
209 209 end
210 210
211 211 def test_operator_all_for_string_custom_field
212 212 query = IssueQuery.new(:project => Project.find(1), :name => '_')
213 213 query.add_filter('cf_2', '*', [''])
214 214 assert query.has_filter?('cf_2')
215 215 issues = find_issues_with_query(query)
216 216 assert !issues.empty?
217 217 assert issues.all? {|i| i.custom_field_value(2).present?}
218 218 end
219 219
220 220 def test_numeric_filter_should_not_accept_non_numeric_values
221 221 query = IssueQuery.new(:name => '_')
222 222 query.add_filter('estimated_hours', '=', ['a'])
223 223
224 224 assert query.has_filter?('estimated_hours')
225 225 assert !query.valid?
226 226 end
227 227
228 228 def test_operator_is_on_float
229 229 Issue.where(:id => 2).update_all("estimated_hours = 171.2")
230 230 query = IssueQuery.new(:name => '_')
231 231 query.add_filter('estimated_hours', '=', ['171.20'])
232 232 issues = find_issues_with_query(query)
233 233 assert_equal 1, issues.size
234 234 assert_equal 2, issues.first.id
235 235 end
236 236
237 237 def test_operator_is_on_integer_custom_field
238 238 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
239 239 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
240 240 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
241 241 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
242 242
243 243 query = IssueQuery.new(:name => '_')
244 244 query.add_filter("cf_#{f.id}", '=', ['12'])
245 245 issues = find_issues_with_query(query)
246 246 assert_equal 1, issues.size
247 247 assert_equal 2, issues.first.id
248 248 end
249 249
250 250 def test_operator_is_on_integer_custom_field_should_accept_negative_value
251 251 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
252 252 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
253 253 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
254 254 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
255 255
256 256 query = IssueQuery.new(:name => '_')
257 257 query.add_filter("cf_#{f.id}", '=', ['-12'])
258 258 assert query.valid?
259 259 issues = find_issues_with_query(query)
260 260 assert_equal 1, issues.size
261 261 assert_equal 2, issues.first.id
262 262 end
263 263
264 264 def test_operator_is_on_float_custom_field
265 265 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
266 266 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
267 267 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
268 268 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
269 269
270 270 query = IssueQuery.new(:name => '_')
271 271 query.add_filter("cf_#{f.id}", '=', ['12.7'])
272 272 issues = find_issues_with_query(query)
273 273 assert_equal 1, issues.size
274 274 assert_equal 2, issues.first.id
275 275 end
276 276
277 277 def test_operator_is_on_float_custom_field_should_accept_negative_value
278 278 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
279 279 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
280 280 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
281 281 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
282 282
283 283 query = IssueQuery.new(:name => '_')
284 284 query.add_filter("cf_#{f.id}", '=', ['-12.7'])
285 285 assert query.valid?
286 286 issues = find_issues_with_query(query)
287 287 assert_equal 1, issues.size
288 288 assert_equal 2, issues.first.id
289 289 end
290 290
291 291 def test_operator_is_on_multi_list_custom_field
292 292 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
293 293 :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
294 294 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
295 295 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
296 296 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
297 297
298 298 query = IssueQuery.new(:name => '_')
299 299 query.add_filter("cf_#{f.id}", '=', ['value1'])
300 300 issues = find_issues_with_query(query)
301 301 assert_equal [1, 3], issues.map(&:id).sort
302 302
303 303 query = IssueQuery.new(:name => '_')
304 304 query.add_filter("cf_#{f.id}", '=', ['value2'])
305 305 issues = find_issues_with_query(query)
306 306 assert_equal [1], issues.map(&:id).sort
307 307 end
308 308
309 309 def test_operator_is_not_on_multi_list_custom_field
310 310 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
311 311 :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
312 312 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
313 313 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
314 314 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
315 315
316 316 query = IssueQuery.new(:name => '_')
317 317 query.add_filter("cf_#{f.id}", '!', ['value1'])
318 318 issues = find_issues_with_query(query)
319 319 assert !issues.map(&:id).include?(1)
320 320 assert !issues.map(&:id).include?(3)
321 321
322 322 query = IssueQuery.new(:name => '_')
323 323 query.add_filter("cf_#{f.id}", '!', ['value2'])
324 324 issues = find_issues_with_query(query)
325 325 assert !issues.map(&:id).include?(1)
326 326 assert issues.map(&:id).include?(3)
327 327 end
328 328
329 329 def test_operator_is_on_is_private_field
330 330 # is_private filter only available for those who can set issues private
331 331 User.current = User.find(2)
332 332
333 333 query = IssueQuery.new(:name => '_')
334 334 assert query.available_filters.key?('is_private')
335 335
336 336 query.add_filter("is_private", '=', ['1'])
337 337 issues = find_issues_with_query(query)
338 338 assert issues.any?
339 339 assert_nil issues.detect {|issue| !issue.is_private?}
340 340 ensure
341 341 User.current = nil
342 342 end
343 343
344 344 def test_operator_is_not_on_is_private_field
345 345 # is_private filter only available for those who can set issues private
346 346 User.current = User.find(2)
347 347
348 348 query = IssueQuery.new(:name => '_')
349 349 assert query.available_filters.key?('is_private')
350 350
351 351 query.add_filter("is_private", '!', ['1'])
352 352 issues = find_issues_with_query(query)
353 353 assert issues.any?
354 354 assert_nil issues.detect {|issue| issue.is_private?}
355 355 ensure
356 356 User.current = nil
357 357 end
358 358
359 359 def test_operator_greater_than
360 360 query = IssueQuery.new(:project => Project.find(1), :name => '_')
361 361 query.add_filter('done_ratio', '>=', ['40'])
362 362 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
363 363 find_issues_with_query(query)
364 364 end
365 365
366 366 def test_operator_greater_than_a_float
367 367 query = IssueQuery.new(:project => Project.find(1), :name => '_')
368 368 query.add_filter('estimated_hours', '>=', ['40.5'])
369 369 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
370 370 find_issues_with_query(query)
371 371 end
372 372
373 373 def test_operator_greater_than_on_int_custom_field
374 374 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
375 375 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
376 376 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
377 377 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
378 378
379 379 query = IssueQuery.new(:project => Project.find(1), :name => '_')
380 380 query.add_filter("cf_#{f.id}", '>=', ['8'])
381 381 issues = find_issues_with_query(query)
382 382 assert_equal 1, issues.size
383 383 assert_equal 2, issues.first.id
384 384 end
385 385
386 386 def test_operator_lesser_than
387 387 query = IssueQuery.new(:project => Project.find(1), :name => '_')
388 388 query.add_filter('done_ratio', '<=', ['30'])
389 389 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
390 390 find_issues_with_query(query)
391 391 end
392 392
393 393 def test_operator_lesser_than_on_custom_field
394 394 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
395 395 query = IssueQuery.new(:project => Project.find(1), :name => '_')
396 396 query.add_filter("cf_#{f.id}", '<=', ['30'])
397 397 assert_match /CAST.+ <= 30\.0/, query.statement
398 398 find_issues_with_query(query)
399 399 end
400 400
401 401 def test_operator_lesser_than_on_date_custom_field
402 402 f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
403 403 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11')
404 404 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14')
405 405 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
406 406
407 407 query = IssueQuery.new(:project => Project.find(1), :name => '_')
408 408 query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
409 409 issue_ids = find_issues_with_query(query).map(&:id)
410 410 assert_include 1, issue_ids
411 411 assert_not_include 2, issue_ids
412 412 assert_not_include 3, issue_ids
413 413 end
414 414
415 415 def test_operator_between
416 416 query = IssueQuery.new(:project => Project.find(1), :name => '_')
417 417 query.add_filter('done_ratio', '><', ['30', '40'])
418 418 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
419 419 find_issues_with_query(query)
420 420 end
421 421
422 422 def test_operator_between_on_custom_field
423 423 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
424 424 query = IssueQuery.new(:project => Project.find(1), :name => '_')
425 425 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
426 426 assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement
427 427 find_issues_with_query(query)
428 428 end
429 429
430 430 def test_date_filter_should_not_accept_non_date_values
431 431 query = IssueQuery.new(:name => '_')
432 432 query.add_filter('created_on', '=', ['a'])
433 433
434 434 assert query.has_filter?('created_on')
435 435 assert !query.valid?
436 436 end
437 437
438 438 def test_date_filter_should_not_accept_invalid_date_values
439 439 query = IssueQuery.new(:name => '_')
440 440 query.add_filter('created_on', '=', ['2011-01-34'])
441 441
442 442 assert query.has_filter?('created_on')
443 443 assert !query.valid?
444 444 end
445 445
446 446 def test_relative_date_filter_should_not_accept_non_integer_values
447 447 query = IssueQuery.new(:name => '_')
448 448 query.add_filter('created_on', '>t-', ['a'])
449 449
450 450 assert query.has_filter?('created_on')
451 451 assert !query.valid?
452 452 end
453 453
454 454 def test_operator_date_equals
455 455 query = IssueQuery.new(:name => '_')
456 456 query.add_filter('due_date', '=', ['2011-07-10'])
457 457 assert_match /issues\.due_date > '#{quoted_date "2011-07-09"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?/,
458 458 query.statement
459 459 find_issues_with_query(query)
460 460 end
461 461
462 462 def test_operator_date_lesser_than
463 463 query = IssueQuery.new(:name => '_')
464 464 query.add_filter('due_date', '<=', ['2011-07-10'])
465 465 assert_match /issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?/, query.statement
466 466 find_issues_with_query(query)
467 467 end
468 468
469 469 def test_operator_date_lesser_than_with_timestamp
470 470 query = IssueQuery.new(:name => '_')
471 471 query.add_filter('updated_on', '<=', ['2011-07-10T19:13:52'])
472 472 assert_match /issues\.updated_on <= '#{quoted_date "2011-07-10"} 19:13:52/, query.statement
473 473 find_issues_with_query(query)
474 474 end
475 475
476 476 def test_operator_date_greater_than
477 477 query = IssueQuery.new(:name => '_')
478 478 query.add_filter('due_date', '>=', ['2011-07-10'])
479 479 assert_match /issues\.due_date > '#{quoted_date "2011-07-09"} 23:59:59(\.\d+)?'/, query.statement
480 480 find_issues_with_query(query)
481 481 end
482 482
483 483 def test_operator_date_greater_than_with_timestamp
484 484 query = IssueQuery.new(:name => '_')
485 485 query.add_filter('updated_on', '>=', ['2011-07-10T19:13:52'])
486 486 assert_match /issues\.updated_on > '#{quoted_date "2011-07-10"} 19:13:51(\.0+)?'/, query.statement
487 487 find_issues_with_query(query)
488 488 end
489 489
490 490 def test_operator_date_between
491 491 query = IssueQuery.new(:name => '_')
492 492 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
493 493 assert_match /issues\.due_date > '#{quoted_date "2011-06-22"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?'/,
494 494 query.statement
495 495 find_issues_with_query(query)
496 496 end
497 497
498 498 def test_operator_in_more_than
499 499 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
500 500 query = IssueQuery.new(:project => Project.find(1), :name => '_')
501 501 query.add_filter('due_date', '>t+', ['15'])
502 502 issues = find_issues_with_query(query)
503 503 assert !issues.empty?
504 504 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
505 505 end
506 506
507 507 def test_operator_in_less_than
508 508 query = IssueQuery.new(:project => Project.find(1), :name => '_')
509 509 query.add_filter('due_date', '<t+', ['15'])
510 510 issues = find_issues_with_query(query)
511 511 assert !issues.empty?
512 512 issues.each {|issue| assert(issue.due_date <= (Date.today + 15))}
513 513 end
514 514
515 515 def test_operator_in_the_next_days
516 516 query = IssueQuery.new(:project => Project.find(1), :name => '_')
517 517 query.add_filter('due_date', '><t+', ['15'])
518 518 issues = find_issues_with_query(query)
519 519 assert !issues.empty?
520 520 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
521 521 end
522 522
523 523 def test_operator_less_than_ago
524 524 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
525 525 query = IssueQuery.new(:project => Project.find(1), :name => '_')
526 526 query.add_filter('due_date', '>t-', ['3'])
527 527 issues = find_issues_with_query(query)
528 528 assert !issues.empty?
529 529 issues.each {|issue| assert(issue.due_date >= (Date.today - 3))}
530 530 end
531 531
532 532 def test_operator_in_the_past_days
533 533 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
534 534 query = IssueQuery.new(:project => Project.find(1), :name => '_')
535 535 query.add_filter('due_date', '><t-', ['3'])
536 536 issues = find_issues_with_query(query)
537 537 assert !issues.empty?
538 538 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
539 539 end
540 540
541 541 def test_operator_more_than_ago
542 542 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
543 543 query = IssueQuery.new(:project => Project.find(1), :name => '_')
544 544 query.add_filter('due_date', '<t-', ['10'])
545 545 assert query.statement.include?("#{Issue.table_name}.due_date <=")
546 546 issues = find_issues_with_query(query)
547 547 assert !issues.empty?
548 548 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
549 549 end
550 550
551 551 def test_operator_in
552 552 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
553 553 query = IssueQuery.new(:project => Project.find(1), :name => '_')
554 554 query.add_filter('due_date', 't+', ['2'])
555 555 issues = find_issues_with_query(query)
556 556 assert !issues.empty?
557 557 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
558 558 end
559 559
560 560 def test_operator_ago
561 561 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
562 562 query = IssueQuery.new(:project => Project.find(1), :name => '_')
563 563 query.add_filter('due_date', 't-', ['3'])
564 564 issues = find_issues_with_query(query)
565 565 assert !issues.empty?
566 566 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
567 567 end
568 568
569 569 def test_operator_today
570 570 query = IssueQuery.new(:project => Project.find(1), :name => '_')
571 571 query.add_filter('due_date', 't', [''])
572 572 issues = find_issues_with_query(query)
573 573 assert !issues.empty?
574 574 issues.each {|issue| assert_equal Date.today, issue.due_date}
575 575 end
576 576
577 577 def test_operator_date_periods
578 578 %w(t ld w lw l2w m lm y).each do |operator|
579 579 query = IssueQuery.new(:name => '_')
580 580 query.add_filter('due_date', operator, [''])
581 581 assert query.valid?
582 582 assert query.issues
583 583 end
584 584 end
585 585
586 586 def test_operator_datetime_periods
587 587 %w(t ld w lw l2w m lm y).each do |operator|
588 588 query = IssueQuery.new(:name => '_')
589 589 query.add_filter('created_on', operator, [''])
590 590 assert query.valid?
591 591 assert query.issues
592 592 end
593 593 end
594 594
595 595 def test_operator_contains
596 596 issue = Issue.generate!(:subject => 'AbCdEfG')
597 597
598 598 query = IssueQuery.new(:name => '_')
599 599 query.add_filter('subject', '~', ['cdeF'])
600 600 result = find_issues_with_query(query)
601 601 assert_include issue, result
602 602 result.each {|issue| assert issue.subject.downcase.include?('cdef') }
603 603 end
604 604
605 605 def test_operator_does_not_contain
606 606 issue = Issue.generate!(:subject => 'AbCdEfG')
607 607
608 608 query = IssueQuery.new(:name => '_')
609 609 query.add_filter('subject', '!~', ['cdeF'])
610 610 result = find_issues_with_query(query)
611 611 assert_not_include issue, result
612 612 end
613 613
614 614 def test_range_for_this_week_with_week_starting_on_monday
615 615 I18n.locale = :fr
616 616 assert_equal '1', I18n.t(:general_first_day_of_week)
617 617
618 618 Date.stubs(:today).returns(Date.parse('2011-04-29'))
619 619
620 620 query = IssueQuery.new(:project => Project.find(1), :name => '_')
621 621 query.add_filter('due_date', 'w', [''])
622 622 assert_match /issues\.due_date > '#{quoted_date "2011-04-24"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-05-01"} 23:59:59(\.\d+)?/,
623 623 query.statement
624 624 I18n.locale = :en
625 625 end
626 626
627 627 def test_range_for_this_week_with_week_starting_on_sunday
628 628 I18n.locale = :en
629 629 assert_equal '7', I18n.t(:general_first_day_of_week)
630 630
631 631 Date.stubs(:today).returns(Date.parse('2011-04-29'))
632 632
633 633 query = IssueQuery.new(:project => Project.find(1), :name => '_')
634 634 query.add_filter('due_date', 'w', [''])
635 635 assert_match /issues\.due_date > '#{quoted_date "2011-04-23"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-04-30"} 23:59:59(\.\d+)?/,
636 636 query.statement
637 637 end
638 638
639 639 def test_filter_assigned_to_me
640 640 user = User.find(2)
641 641 group = Group.find(10)
642 642 User.current = user
643 643 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
644 644 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
645 645 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
646 646 group.users << user
647 647
648 648 query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
649 649 result = query.issues
650 650 assert_equal Issue.visible.where(:assigned_to_id => ([2] + user.reload.group_ids)).sort_by(&:id), result.sort_by(&:id)
651 651
652 652 assert result.include?(i1)
653 653 assert result.include?(i2)
654 654 assert !result.include?(i3)
655 655 end
656 656
657 657 def test_user_custom_field_filtered_on_me
658 658 User.current = User.find(2)
659 659 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
660 660 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
661 661 issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
662 662
663 663 query = IssueQuery.new(:name => '_', :project => Project.find(1))
664 664 filter = query.available_filters["cf_#{cf.id}"]
665 665 assert_not_nil filter
666 666 assert_include 'me', filter[:values].map{|v| v[1]}
667 667
668 668 query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
669 669 result = query.issues
670 670 assert_equal 1, result.size
671 671 assert_equal issue1, result.first
672 672 end
673 673
674 674 def test_filter_on_me_by_anonymous_user
675 675 User.current = nil
676 676 query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
677 677 assert_equal [], query.issues
678 678 end
679 679
680 680 def test_filter_my_projects
681 681 User.current = User.find(2)
682 682 query = IssueQuery.new(:name => '_')
683 683 filter = query.available_filters['project_id']
684 684 assert_not_nil filter
685 685 assert_include 'mine', filter[:values].map{|v| v[1]}
686 686
687 687 query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
688 688 result = query.issues
689 689 assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
690 690 end
691 691
692 692 def test_filter_watched_issues
693 693 User.current = User.find(1)
694 694 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
695 695 result = find_issues_with_query(query)
696 696 assert_not_nil result
697 697 assert !result.empty?
698 698 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
699 699 User.current = nil
700 700 end
701 701
702 702 def test_filter_unwatched_issues
703 703 User.current = User.find(1)
704 704 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
705 705 result = find_issues_with_query(query)
706 706 assert_not_nil result
707 707 assert !result.empty?
708 708 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
709 709 User.current = nil
710 710 end
711 711
712 712 def test_filter_on_custom_field_should_ignore_projects_with_field_disabled
713 713 field = IssueCustomField.generate!(:trackers => Tracker.all, :project_ids => [1, 3, 4], :is_for_all => false, :is_filter => true)
714 714 Issue.generate!(:project_id => 3, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
715 715 Issue.generate!(:project_id => 4, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
716 716
717 717 query = IssueQuery.new(:name => '_', :project => Project.find(1))
718 718 query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
719 719 assert_equal 2, find_issues_with_query(query).size
720 720
721 721 field.project_ids = [1, 3] # Disable the field for project 4
722 722 field.save!
723 723 assert_equal 1, find_issues_with_query(query).size
724 724 end
725 725
726 726 def test_filter_on_custom_field_should_ignore_trackers_with_field_disabled
727 727 field = IssueCustomField.generate!(:tracker_ids => [1, 2], :is_for_all => true, :is_filter => true)
728 728 Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => 'Foo'})
729 729 Issue.generate!(:project_id => 1, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
730 730
731 731 query = IssueQuery.new(:name => '_', :project => Project.find(1))
732 732 query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
733 733 assert_equal 2, find_issues_with_query(query).size
734 734
735 735 field.tracker_ids = [1] # Disable the field for tracker 2
736 736 field.save!
737 737 assert_equal 1, find_issues_with_query(query).size
738 738 end
739 739
740 740 def test_filter_on_project_custom_field
741 741 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
742 742 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
743 743 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
744 744
745 745 query = IssueQuery.new(:name => '_')
746 746 filter_name = "project.cf_#{field.id}"
747 747 assert_include filter_name, query.available_filters.keys
748 748 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
749 749 assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
750 750 end
751 751
752 752 def test_filter_on_author_custom_field
753 753 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
754 754 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
755 755
756 756 query = IssueQuery.new(:name => '_')
757 757 filter_name = "author.cf_#{field.id}"
758 758 assert_include filter_name, query.available_filters.keys
759 759 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
760 760 assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
761 761 end
762 762
763 763 def test_filter_on_assigned_to_custom_field
764 764 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
765 765 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
766 766
767 767 query = IssueQuery.new(:name => '_')
768 768 filter_name = "assigned_to.cf_#{field.id}"
769 769 assert_include filter_name, query.available_filters.keys
770 770 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
771 771 assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
772 772 end
773 773
774 774 def test_filter_on_fixed_version_custom_field
775 775 field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
776 776 CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
777 777
778 778 query = IssueQuery.new(:name => '_')
779 779 filter_name = "fixed_version.cf_#{field.id}"
780 780 assert_include filter_name, query.available_filters.keys
781 781 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
782 782 assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
783 783 end
784 784
785 785 def test_filter_on_relations_with_a_specific_issue
786 786 IssueRelation.delete_all
787 787 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
788 788 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
789 789
790 790 query = IssueQuery.new(:name => '_')
791 791 query.filters = {"relates" => {:operator => '=', :values => ['1']}}
792 792 assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
793 793
794 794 query = IssueQuery.new(:name => '_')
795 795 query.filters = {"relates" => {:operator => '=', :values => ['2']}}
796 796 assert_equal [1], find_issues_with_query(query).map(&:id).sort
797 797 end
798 798
799 799 def test_filter_on_relations_with_any_issues_in_a_project
800 800 IssueRelation.delete_all
801 801 with_settings :cross_project_issue_relations => '1' do
802 802 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
803 803 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first)
804 804 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
805 805 end
806 806
807 807 query = IssueQuery.new(:name => '_')
808 808 query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
809 809 assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort
810 810
811 811 query = IssueQuery.new(:name => '_')
812 812 query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
813 813 assert_equal [1], find_issues_with_query(query).map(&:id).sort
814 814
815 815 query = IssueQuery.new(:name => '_')
816 816 query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
817 817 assert_equal [], find_issues_with_query(query).map(&:id).sort
818 818 end
819 819
820 820 def test_filter_on_relations_with_any_issues_not_in_a_project
821 821 IssueRelation.delete_all
822 822 with_settings :cross_project_issue_relations => '1' do
823 823 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
824 824 #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first)
825 825 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
826 826 end
827 827
828 828 query = IssueQuery.new(:name => '_')
829 829 query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
830 830 assert_equal [1], find_issues_with_query(query).map(&:id).sort
831 831 end
832 832
833 833 def test_filter_on_relations_with_no_issues_in_a_project
834 834 IssueRelation.delete_all
835 835 with_settings :cross_project_issue_relations => '1' do
836 836 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
837 837 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first)
838 838 IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
839 839 end
840 840
841 841 query = IssueQuery.new(:name => '_')
842 842 query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
843 843 ids = find_issues_with_query(query).map(&:id).sort
844 844 assert_include 2, ids
845 845 assert_not_include 1, ids
846 846 assert_not_include 3, ids
847 847 end
848 848
849 def test_filter_on_relations_with_any_open_issues
850 IssueRelation.delete_all
851 # Issue 1 is blocked by 8, which is closed
852 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
853 # Issue 2 is blocked by 3, which is open
854 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))
855
856 query = IssueQuery.new(:name => '_')
857 query.filters = {"blocked" => {:operator => "*o", :values => ['']}}
858 ids = find_issues_with_query(query).map(&:id)
859 assert_equal [], ids & [1]
860 assert_include 2, ids
861 end
862
863 def test_filter_on_relations_with_no_open_issues
864 IssueRelation.delete_all
865 # Issue 1 is blocked by 8, which is closed
866 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
867 # Issue 2 is blocked by 3, which is open
868 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))
869
870 query = IssueQuery.new(:name => '_')
871 query.filters = {"blocked" => {:operator => "!o", :values => ['']}}
872 ids = find_issues_with_query(query).map(&:id)
873 assert_equal [], ids & [2]
874 assert_include 1, ids
875 end
876
849 877 def test_filter_on_relations_with_no_issues
850 878 IssueRelation.delete_all
851 879 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
852 880 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
853 881
854 882 query = IssueQuery.new(:name => '_')
855 883 query.filters = {"relates" => {:operator => '!*', :values => ['']}}
856 884 ids = find_issues_with_query(query).map(&:id)
857 885 assert_equal [], ids & [1, 2, 3]
858 886 assert_include 4, ids
859 887 end
860 888
861 889 def test_filter_on_relations_with_any_issues
862 890 IssueRelation.delete_all
863 891 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
864 892 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
865 893
866 894 query = IssueQuery.new(:name => '_')
867 895 query.filters = {"relates" => {:operator => '*', :values => ['']}}
868 896 assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
869 897 end
870 898
871 899 def test_filter_on_relations_should_not_ignore_other_filter
872 900 issue = Issue.generate!
873 901 issue1 = Issue.generate!(:status_id => 1)
874 902 issue2 = Issue.generate!(:status_id => 2)
875 903 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue1)
876 904 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue2)
877 905
878 906 query = IssueQuery.new(:name => '_')
879 907 query.filters = {
880 908 "status_id" => {:operator => '=', :values => ['1']},
881 909 "relates" => {:operator => '=', :values => [issue.id.to_s]}
882 910 }
883 911 assert_equal [issue1], find_issues_with_query(query)
884 912 end
885 913
886 914 def test_filter_on_parent
887 915 Issue.delete_all
888 916 parent = Issue.generate_with_descendants!
889 917
890 918
891 919 query = IssueQuery.new(:name => '_')
892 920 query.filters = {"parent_id" => {:operator => '=', :values => [parent.id.to_s]}}
893 921 assert_equal parent.children.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
894 922
895 923 query.filters = {"parent_id" => {:operator => '~', :values => [parent.id.to_s]}}
896 924 assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
897 925
898 926 query.filters = {"parent_id" => {:operator => '*', :values => ['']}}
899 927 assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
900 928
901 929 query.filters = {"parent_id" => {:operator => '!*', :values => ['']}}
902 930 assert_equal [parent.id], find_issues_with_query(query).map(&:id).sort
903 931 end
904 932
905 933 def test_filter_on_invalid_parent_should_return_no_results
906 934 query = IssueQuery.new(:name => '_')
907 935 query.filters = {"parent_id" => {:operator => '=', :values => '99999999999'}}
908 936 assert_equal [], find_issues_with_query(query).map(&:id).sort
909 937
910 938 query.filters = {"parent_id" => {:operator => '~', :values => '99999999999'}}
911 939 assert_equal [], find_issues_with_query(query)
912 940 end
913 941
914 942 def test_filter_on_child
915 943 Issue.delete_all
916 944 parent = Issue.generate_with_descendants!
917 945 child, leaf = parent.children.sort_by(&:id)
918 946 grandchild = child.children.first
919 947
920 948
921 949 query = IssueQuery.new(:name => '_')
922 950 query.filters = {"child_id" => {:operator => '=', :values => [grandchild.id.to_s]}}
923 951 assert_equal [child.id], find_issues_with_query(query).map(&:id).sort
924 952
925 953 query.filters = {"child_id" => {:operator => '~', :values => [grandchild.id.to_s]}}
926 954 assert_equal [parent, child].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
927 955
928 956 query.filters = {"child_id" => {:operator => '*', :values => ['']}}
929 957 assert_equal [parent, child].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
930 958
931 959 query.filters = {"child_id" => {:operator => '!*', :values => ['']}}
932 960 assert_equal [grandchild, leaf].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
933 961 end
934 962
935 963 def test_filter_on_invalid_child_should_return_no_results
936 964 query = IssueQuery.new(:name => '_')
937 965 query.filters = {"child_id" => {:operator => '=', :values => '99999999999'}}
938 966 assert_equal [], find_issues_with_query(query)
939 967
940 968 query.filters = {"child_id" => {:operator => '~', :values => '99999999999'}}
941 969 assert_equal [].map(&:id).sort, find_issues_with_query(query)
942 970 end
943 971
944 972 def test_statement_should_be_nil_with_no_filters
945 973 q = IssueQuery.new(:name => '_')
946 974 q.filters = {}
947 975
948 976 assert q.valid?
949 977 assert_nil q.statement
950 978 end
951 979
952 980 def test_available_filters_as_json_should_include_missing_assigned_to_id_values
953 981 user = User.generate!
954 982 with_current_user User.find(1) do
955 983 q = IssueQuery.new
956 984 q.filters = {"assigned_to_id" => {:operator => '=', :values => user.id.to_s}}
957 985
958 986 filters = q.available_filters_as_json
959 987 assert_include [user.name, user.id.to_s], filters['assigned_to_id']['values']
960 988 end
961 989 end
962 990
963 991 def test_available_filters_as_json_should_include_missing_author_id_values
964 992 user = User.generate!
965 993 with_current_user User.find(1) do
966 994 q = IssueQuery.new
967 995 q.filters = {"author_id" => {:operator => '=', :values => user.id.to_s}}
968 996
969 997 filters = q.available_filters_as_json
970 998 assert_include [user.name, user.id.to_s], filters['author_id']['values']
971 999 end
972 1000 end
973 1001
974 1002 def test_default_columns
975 1003 q = IssueQuery.new
976 1004 assert q.columns.any?
977 1005 assert q.inline_columns.any?
978 1006 assert q.block_columns.empty?
979 1007 end
980 1008
981 1009 def test_set_column_names
982 1010 q = IssueQuery.new
983 1011 q.column_names = ['tracker', :subject, '', 'unknonw_column']
984 1012 assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name}
985 1013 end
986 1014
987 1015 def test_has_column_should_accept_a_column_name
988 1016 q = IssueQuery.new
989 1017 q.column_names = ['tracker', :subject]
990 1018 assert q.has_column?(:tracker)
991 1019 assert !q.has_column?(:category)
992 1020 end
993 1021
994 1022 def test_has_column_should_accept_a_column
995 1023 q = IssueQuery.new
996 1024 q.column_names = ['tracker', :subject]
997 1025
998 1026 tracker_column = q.available_columns.detect {|c| c.name==:tracker}
999 1027 assert_kind_of QueryColumn, tracker_column
1000 1028 category_column = q.available_columns.detect {|c| c.name==:category}
1001 1029 assert_kind_of QueryColumn, category_column
1002 1030
1003 1031 assert q.has_column?(tracker_column)
1004 1032 assert !q.has_column?(category_column)
1005 1033 end
1006 1034
1007 1035 def test_inline_and_block_columns
1008 1036 q = IssueQuery.new
1009 1037 q.column_names = ['subject', 'description', 'tracker']
1010 1038
1011 1039 assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name)
1012 1040 assert_equal [:description], q.block_columns.map(&:name)
1013 1041 end
1014 1042
1015 1043 def test_custom_field_columns_should_be_inline
1016 1044 q = IssueQuery.new
1017 1045 columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn}
1018 1046 assert columns.any?
1019 1047 assert_nil columns.detect {|column| !column.inline?}
1020 1048 end
1021 1049
1022 1050 def test_query_should_preload_spent_hours
1023 1051 q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours])
1024 1052 assert q.has_column?(:spent_hours)
1025 1053 issues = q.issues
1026 1054 assert_not_nil issues.first.instance_variable_get("@spent_hours")
1027 1055 end
1028 1056
1029 1057 def test_groupable_columns_should_include_custom_fields
1030 1058 q = IssueQuery.new
1031 1059 column = q.groupable_columns.detect {|c| c.name == :cf_1}
1032 1060 assert_not_nil column
1033 1061 assert_kind_of QueryCustomFieldColumn, column
1034 1062 end
1035 1063
1036 1064 def test_groupable_columns_should_not_include_multi_custom_fields
1037 1065 field = CustomField.find(1)
1038 1066 field.update_attribute :multiple, true
1039 1067
1040 1068 q = IssueQuery.new
1041 1069 column = q.groupable_columns.detect {|c| c.name == :cf_1}
1042 1070 assert_nil column
1043 1071 end
1044 1072
1045 1073 def test_groupable_columns_should_include_user_custom_fields
1046 1074 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
1047 1075
1048 1076 q = IssueQuery.new
1049 1077 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
1050 1078 end
1051 1079
1052 1080 def test_groupable_columns_should_include_version_custom_fields
1053 1081 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
1054 1082
1055 1083 q = IssueQuery.new
1056 1084 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
1057 1085 end
1058 1086
1059 1087 def test_grouped_with_valid_column
1060 1088 q = IssueQuery.new(:group_by => 'status')
1061 1089 assert q.grouped?
1062 1090 assert_not_nil q.group_by_column
1063 1091 assert_equal :status, q.group_by_column.name
1064 1092 assert_not_nil q.group_by_statement
1065 1093 assert_equal 'status', q.group_by_statement
1066 1094 end
1067 1095
1068 1096 def test_grouped_with_invalid_column
1069 1097 q = IssueQuery.new(:group_by => 'foo')
1070 1098 assert !q.grouped?
1071 1099 assert_nil q.group_by_column
1072 1100 assert_nil q.group_by_statement
1073 1101 end
1074 1102
1075 1103 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
1076 1104 with_settings :user_format => 'lastname_comma_firstname' do
1077 1105 q = IssueQuery.new
1078 1106 assert q.sortable_columns.has_key?('assigned_to')
1079 1107 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
1080 1108 end
1081 1109 end
1082 1110
1083 1111 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
1084 1112 with_settings :user_format => 'lastname_comma_firstname' do
1085 1113 q = IssueQuery.new
1086 1114 assert q.sortable_columns.has_key?('author')
1087 1115 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
1088 1116 end
1089 1117 end
1090 1118
1091 1119 def test_sortable_columns_should_include_custom_field
1092 1120 q = IssueQuery.new
1093 1121 assert q.sortable_columns['cf_1']
1094 1122 end
1095 1123
1096 1124 def test_sortable_columns_should_not_include_multi_custom_field
1097 1125 field = CustomField.find(1)
1098 1126 field.update_attribute :multiple, true
1099 1127
1100 1128 q = IssueQuery.new
1101 1129 assert !q.sortable_columns['cf_1']
1102 1130 end
1103 1131
1104 1132 def test_default_sort
1105 1133 q = IssueQuery.new
1106 1134 assert_equal [], q.sort_criteria
1107 1135 end
1108 1136
1109 1137 def test_set_sort_criteria_with_hash
1110 1138 q = IssueQuery.new
1111 1139 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
1112 1140 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1113 1141 end
1114 1142
1115 1143 def test_set_sort_criteria_with_array
1116 1144 q = IssueQuery.new
1117 1145 q.sort_criteria = [['priority', 'desc'], 'tracker']
1118 1146 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1119 1147 end
1120 1148
1121 1149 def test_create_query_with_sort
1122 1150 q = IssueQuery.new(:name => 'Sorted')
1123 1151 q.sort_criteria = [['priority', 'desc'], 'tracker']
1124 1152 assert q.save
1125 1153 q.reload
1126 1154 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1127 1155 end
1128 1156
1129 1157 def test_sort_by_string_custom_field_asc
1130 1158 q = IssueQuery.new
1131 1159 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
1132 1160 assert c
1133 1161 assert c.sortable
1134 1162 issues = q.issues(:order => "#{c.sortable} ASC")
1135 1163 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
1136 1164 assert !values.empty?
1137 1165 assert_equal values.sort, values
1138 1166 end
1139 1167
1140 1168 def test_sort_by_string_custom_field_desc
1141 1169 q = IssueQuery.new
1142 1170 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
1143 1171 assert c
1144 1172 assert c.sortable
1145 1173 issues = q.issues(:order => "#{c.sortable} DESC")
1146 1174 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
1147 1175 assert !values.empty?
1148 1176 assert_equal values.sort.reverse, values
1149 1177 end
1150 1178
1151 1179 def test_sort_by_float_custom_field_asc
1152 1180 q = IssueQuery.new
1153 1181 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
1154 1182 assert c
1155 1183 assert c.sortable
1156 1184 issues = q.issues(:order => "#{c.sortable} ASC")
1157 1185 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
1158 1186 assert !values.empty?
1159 1187 assert_equal values.sort, values
1160 1188 end
1161 1189
1162 1190 def test_set_totalable_names
1163 1191 q = IssueQuery.new
1164 1192 q.totalable_names = ['estimated_hours', :spent_hours, '']
1165 1193 assert_equal [:estimated_hours, :spent_hours], q.totalable_columns.map(&:name)
1166 1194 end
1167 1195
1168 1196 def test_totalable_columns_should_default_to_settings
1169 1197 with_settings :issue_list_default_totals => ['estimated_hours'] do
1170 1198 q = IssueQuery.new
1171 1199 assert_equal [:estimated_hours], q.totalable_columns.map(&:name)
1172 1200 end
1173 1201 end
1174 1202
1175 1203 def test_available_totalable_columns_should_include_estimated_hours
1176 1204 q = IssueQuery.new
1177 1205 assert_include :estimated_hours, q.available_totalable_columns.map(&:name)
1178 1206 end
1179 1207
1180 1208 def test_available_totalable_columns_should_include_spent_hours
1181 1209 User.current = User.find(1)
1182 1210
1183 1211 q = IssueQuery.new
1184 1212 assert_include :spent_hours, q.available_totalable_columns.map(&:name)
1185 1213 end
1186 1214
1187 1215 def test_available_totalable_columns_should_include_int_custom_field
1188 1216 field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
1189 1217 q = IssueQuery.new
1190 1218 assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name)
1191 1219 end
1192 1220
1193 1221 def test_available_totalable_columns_should_include_float_custom_field
1194 1222 field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true)
1195 1223 q = IssueQuery.new
1196 1224 assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name)
1197 1225 end
1198 1226
1199 1227 def test_total_for_estimated_hours
1200 1228 Issue.delete_all
1201 1229 Issue.generate!(:estimated_hours => 5.5)
1202 1230 Issue.generate!(:estimated_hours => 1.1)
1203 1231 Issue.generate!
1204 1232
1205 1233 q = IssueQuery.new
1206 1234 assert_equal 6.6, q.total_for(:estimated_hours)
1207 1235 end
1208 1236
1209 1237 def test_total_by_group_for_estimated_hours
1210 1238 Issue.delete_all
1211 1239 Issue.generate!(:estimated_hours => 5.5, :assigned_to_id => 2)
1212 1240 Issue.generate!(:estimated_hours => 1.1, :assigned_to_id => 3)
1213 1241 Issue.generate!(:estimated_hours => 3.5)
1214 1242
1215 1243 q = IssueQuery.new(:group_by => 'assigned_to')
1216 1244 assert_equal(
1217 1245 {nil => 3.5, User.find(2) => 5.5, User.find(3) => 1.1},
1218 1246 q.total_by_group_for(:estimated_hours)
1219 1247 )
1220 1248 end
1221 1249
1222 1250 def test_total_for_spent_hours
1223 1251 TimeEntry.delete_all
1224 1252 TimeEntry.generate!(:hours => 5.5)
1225 1253 TimeEntry.generate!(:hours => 1.1)
1226 1254
1227 1255 q = IssueQuery.new
1228 1256 assert_equal 6.6, q.total_for(:spent_hours)
1229 1257 end
1230 1258
1231 1259 def test_total_by_group_for_spent_hours
1232 1260 TimeEntry.delete_all
1233 1261 TimeEntry.generate!(:hours => 5.5, :issue_id => 1)
1234 1262 TimeEntry.generate!(:hours => 1.1, :issue_id => 2)
1235 1263 Issue.where(:id => 1).update_all(:assigned_to_id => 2)
1236 1264 Issue.where(:id => 2).update_all(:assigned_to_id => 3)
1237 1265
1238 1266 q = IssueQuery.new(:group_by => 'assigned_to')
1239 1267 assert_equal(
1240 1268 {User.find(2) => 5.5, User.find(3) => 1.1},
1241 1269 q.total_by_group_for(:spent_hours)
1242 1270 )
1243 1271 end
1244 1272
1245 1273 def test_total_by_project_group_for_spent_hours
1246 1274 TimeEntry.delete_all
1247 1275 TimeEntry.generate!(:hours => 5.5, :issue_id => 1)
1248 1276 TimeEntry.generate!(:hours => 1.1, :issue_id => 2)
1249 1277 Issue.where(:id => 1).update_all(:assigned_to_id => 2)
1250 1278 Issue.where(:id => 2).update_all(:assigned_to_id => 3)
1251 1279
1252 1280 q = IssueQuery.new(:group_by => 'project')
1253 1281 assert_equal(
1254 1282 {Project.find(1) => 6.6},
1255 1283 q.total_by_group_for(:spent_hours)
1256 1284 )
1257 1285 end
1258 1286
1259 1287 def test_total_for_int_custom_field
1260 1288 field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
1261 1289 CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2')
1262 1290 CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
1263 1291 CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '')
1264 1292
1265 1293 q = IssueQuery.new
1266 1294 assert_equal 9, q.total_for("cf_#{field.id}")
1267 1295 end
1268 1296
1269 1297 def test_total_by_group_for_int_custom_field
1270 1298 field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
1271 1299 CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2')
1272 1300 CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
1273 1301 Issue.where(:id => 1).update_all(:assigned_to_id => 2)
1274 1302 Issue.where(:id => 2).update_all(:assigned_to_id => 3)
1275 1303
1276 1304 q = IssueQuery.new(:group_by => 'assigned_to')
1277 1305 assert_equal(
1278 1306 {User.find(2) => 2, User.find(3) => 7},
1279 1307 q.total_by_group_for("cf_#{field.id}")
1280 1308 )
1281 1309 end
1282 1310
1283 1311 def test_total_for_float_custom_field
1284 1312 field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true)
1285 1313 CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2.3')
1286 1314 CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
1287 1315 CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '')
1288 1316
1289 1317 q = IssueQuery.new
1290 1318 assert_equal 9.3, q.total_for("cf_#{field.id}")
1291 1319 end
1292 1320
1293 1321 def test_invalid_query_should_raise_query_statement_invalid_error
1294 1322 q = IssueQuery.new
1295 1323 assert_raise Query::StatementInvalid do
1296 1324 q.issues(:conditions => "foo = 1")
1297 1325 end
1298 1326 end
1299 1327
1300 1328 def test_issue_count
1301 1329 q = IssueQuery.new(:name => '_')
1302 1330 issue_count = q.issue_count
1303 1331 assert_equal q.issues.size, issue_count
1304 1332 end
1305 1333
1306 1334 def test_issue_count_with_archived_issues
1307 1335 p = Project.generate! do |project|
1308 1336 project.status = Project::STATUS_ARCHIVED
1309 1337 end
1310 1338 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
1311 1339 assert !i.visible?
1312 1340
1313 1341 test_issue_count
1314 1342 end
1315 1343
1316 1344 def test_issue_count_by_association_group
1317 1345 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
1318 1346 count_by_group = q.issue_count_by_group
1319 1347 assert_kind_of Hash, count_by_group
1320 1348 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1321 1349 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1322 1350 assert count_by_group.has_key?(User.find(3))
1323 1351 end
1324 1352
1325 1353 def test_issue_count_by_list_custom_field_group
1326 1354 q = IssueQuery.new(:name => '_', :group_by => 'cf_1')
1327 1355 count_by_group = q.issue_count_by_group
1328 1356 assert_kind_of Hash, count_by_group
1329 1357 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1330 1358 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1331 1359 assert count_by_group.has_key?('MySQL')
1332 1360 end
1333 1361
1334 1362 def test_issue_count_by_date_custom_field_group
1335 1363 q = IssueQuery.new(:name => '_', :group_by => 'cf_8')
1336 1364 count_by_group = q.issue_count_by_group
1337 1365 assert_kind_of Hash, count_by_group
1338 1366 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1339 1367 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1340 1368 end
1341 1369
1342 1370 def test_issue_count_with_nil_group_only
1343 1371 Issue.update_all("assigned_to_id = NULL")
1344 1372
1345 1373 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
1346 1374 count_by_group = q.issue_count_by_group
1347 1375 assert_kind_of Hash, count_by_group
1348 1376 assert_equal 1, count_by_group.keys.size
1349 1377 assert_nil count_by_group.keys.first
1350 1378 end
1351 1379
1352 1380 def test_issue_ids
1353 1381 q = IssueQuery.new(:name => '_')
1354 1382 order = "issues.subject, issues.id"
1355 1383 issues = q.issues(:order => order)
1356 1384 assert_equal issues.map(&:id), q.issue_ids(:order => order)
1357 1385 end
1358 1386
1359 1387 def test_label_for
1360 1388 set_language_if_valid 'en'
1361 1389 q = IssueQuery.new
1362 1390 assert_equal 'Assignee', q.label_for('assigned_to_id')
1363 1391 end
1364 1392
1365 1393 def test_label_for_fr
1366 1394 set_language_if_valid 'fr'
1367 1395 q = IssueQuery.new
1368 1396 assert_equal "Assign\xc3\xa9 \xc3\xa0".force_encoding('UTF-8'), q.label_for('assigned_to_id')
1369 1397 end
1370 1398
1371 1399 def test_editable_by
1372 1400 admin = User.find(1)
1373 1401 manager = User.find(2)
1374 1402 developer = User.find(3)
1375 1403
1376 1404 # Public query on project 1
1377 1405 q = IssueQuery.find(1)
1378 1406 assert q.editable_by?(admin)
1379 1407 assert q.editable_by?(manager)
1380 1408 assert !q.editable_by?(developer)
1381 1409
1382 1410 # Private query on project 1
1383 1411 q = IssueQuery.find(2)
1384 1412 assert q.editable_by?(admin)
1385 1413 assert !q.editable_by?(manager)
1386 1414 assert q.editable_by?(developer)
1387 1415
1388 1416 # Private query for all projects
1389 1417 q = IssueQuery.find(3)
1390 1418 assert q.editable_by?(admin)
1391 1419 assert !q.editable_by?(manager)
1392 1420 assert q.editable_by?(developer)
1393 1421
1394 1422 # Public query for all projects
1395 1423 q = IssueQuery.find(4)
1396 1424 assert q.editable_by?(admin)
1397 1425 assert !q.editable_by?(manager)
1398 1426 assert !q.editable_by?(developer)
1399 1427 end
1400 1428
1401 1429 def test_visible_scope
1402 1430 query_ids = IssueQuery.visible(User.anonymous).map(&:id)
1403 1431
1404 1432 assert query_ids.include?(1), 'public query on public project was not visible'
1405 1433 assert query_ids.include?(4), 'public query for all projects was not visible'
1406 1434 assert !query_ids.include?(2), 'private query on public project was visible'
1407 1435 assert !query_ids.include?(3), 'private query for all projects was visible'
1408 1436 assert !query_ids.include?(7), 'public query on private project was visible'
1409 1437 end
1410 1438
1411 1439 def test_query_with_public_visibility_should_be_visible_to_anyone
1412 1440 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PUBLIC)
1413 1441
1414 1442 assert q.visible?(User.anonymous)
1415 1443 assert IssueQuery.visible(User.anonymous).find_by_id(q.id)
1416 1444
1417 1445 assert q.visible?(User.find(7))
1418 1446 assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
1419 1447
1420 1448 assert q.visible?(User.find(2))
1421 1449 assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
1422 1450
1423 1451 assert q.visible?(User.find(1))
1424 1452 assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
1425 1453 end
1426 1454
1427 1455 def test_query_with_roles_visibility_should_be_visible_to_user_with_role
1428 1456 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1,2])
1429 1457
1430 1458 assert !q.visible?(User.anonymous)
1431 1459 assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
1432 1460
1433 1461 assert !q.visible?(User.find(7))
1434 1462 assert_nil IssueQuery.visible(User.find(7)).find_by_id(q.id)
1435 1463
1436 1464 assert q.visible?(User.find(2))
1437 1465 assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
1438 1466
1439 1467 assert q.visible?(User.find(1))
1440 1468 assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
1441 1469 end
1442 1470
1443 1471 def test_query_with_private_visibility_should_be_visible_to_owner
1444 1472 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PRIVATE, :user => User.find(7))
1445 1473
1446 1474 assert !q.visible?(User.anonymous)
1447 1475 assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
1448 1476
1449 1477 assert q.visible?(User.find(7))
1450 1478 assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
1451 1479
1452 1480 assert !q.visible?(User.find(2))
1453 1481 assert_nil IssueQuery.visible(User.find(2)).find_by_id(q.id)
1454 1482
1455 1483 assert q.visible?(User.find(1))
1456 1484 assert_nil IssueQuery.visible(User.find(1)).find_by_id(q.id)
1457 1485 end
1458 1486
1459 1487 test "#available_filters should include users of visible projects in cross-project view" do
1460 1488 users = IssueQuery.new.available_filters["assigned_to_id"]
1461 1489 assert_not_nil users
1462 1490 assert users[:values].map{|u|u[1]}.include?("3")
1463 1491 end
1464 1492
1465 1493 test "#available_filters should include users of subprojects" do
1466 1494 user1 = User.generate!
1467 1495 user2 = User.generate!
1468 1496 project = Project.find(1)
1469 1497 Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
1470 1498
1471 1499 users = IssueQuery.new(:project => project).available_filters["assigned_to_id"]
1472 1500 assert_not_nil users
1473 1501 assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
1474 1502 assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
1475 1503 end
1476 1504
1477 1505 test "#available_filters should include visible projects in cross-project view" do
1478 1506 projects = IssueQuery.new.available_filters["project_id"]
1479 1507 assert_not_nil projects
1480 1508 assert projects[:values].map{|u|u[1]}.include?("1")
1481 1509 end
1482 1510
1483 1511 test "#available_filters should include 'member_of_group' filter" do
1484 1512 query = IssueQuery.new
1485 1513 assert query.available_filters.keys.include?("member_of_group")
1486 1514 assert_equal :list_optional, query.available_filters["member_of_group"][:type]
1487 1515 assert query.available_filters["member_of_group"][:values].present?
1488 1516 assert_equal Group.givable.sort.map {|g| [g.name, g.id.to_s]},
1489 1517 query.available_filters["member_of_group"][:values].sort
1490 1518 end
1491 1519
1492 1520 test "#available_filters should include 'assigned_to_role' filter" do
1493 1521 query = IssueQuery.new
1494 1522 assert query.available_filters.keys.include?("assigned_to_role")
1495 1523 assert_equal :list_optional, query.available_filters["assigned_to_role"][:type]
1496 1524
1497 1525 assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
1498 1526 assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
1499 1527 assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
1500 1528
1501 1529 assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
1502 1530 assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
1503 1531 end
1504 1532
1505 1533 def test_available_filters_should_include_custom_field_according_to_user_visibility
1506 1534 visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
1507 1535 hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
1508 1536
1509 1537 with_current_user User.find(3) do
1510 1538 query = IssueQuery.new
1511 1539 assert_include "cf_#{visible_field.id}", query.available_filters.keys
1512 1540 assert_not_include "cf_#{hidden_field.id}", query.available_filters.keys
1513 1541 end
1514 1542 end
1515 1543
1516 1544 def test_available_columns_should_include_custom_field_according_to_user_visibility
1517 1545 visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
1518 1546 hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
1519 1547
1520 1548 with_current_user User.find(3) do
1521 1549 query = IssueQuery.new
1522 1550 assert_include :"cf_#{visible_field.id}", query.available_columns.map(&:name)
1523 1551 assert_not_include :"cf_#{hidden_field.id}", query.available_columns.map(&:name)
1524 1552 end
1525 1553 end
1526 1554
1527 1555 def setup_member_of_group
1528 1556 Group.destroy_all # No fixtures
1529 1557 @user_in_group = User.generate!
1530 1558 @second_user_in_group = User.generate!
1531 1559 @user_in_group2 = User.generate!
1532 1560 @user_not_in_group = User.generate!
1533 1561
1534 1562 @group = Group.generate!.reload
1535 1563 @group.users << @user_in_group
1536 1564 @group.users << @second_user_in_group
1537 1565
1538 1566 @group2 = Group.generate!.reload
1539 1567 @group2.users << @user_in_group2
1540 1568
1541 1569 @query = IssueQuery.new(:name => '_')
1542 1570 end
1543 1571
1544 1572 test "member_of_group filter should search assigned to for users in the group" do
1545 1573 setup_member_of_group
1546 1574 @query.add_filter('member_of_group', '=', [@group.id.to_s])
1547 1575
1548 1576 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@group.id}')"
1549 1577 assert_find_issues_with_query_is_successful @query
1550 1578 end
1551 1579
1552 1580 test "member_of_group filter should search not assigned to any group member (none)" do
1553 1581 setup_member_of_group
1554 1582 @query.add_filter('member_of_group', '!*', [''])
1555 1583
1556 1584 # Users not in a group
1557 1585 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}','#{@group.id}','#{@group2.id}')"
1558 1586 assert_find_issues_with_query_is_successful @query
1559 1587 end
1560 1588
1561 1589 test "member_of_group filter should search assigned to any group member (all)" do
1562 1590 setup_member_of_group
1563 1591 @query.add_filter('member_of_group', '*', [''])
1564 1592
1565 1593 # Only users in a group
1566 1594 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}','#{@group.id}','#{@group2.id}')"
1567 1595 assert_find_issues_with_query_is_successful @query
1568 1596 end
1569 1597
1570 1598 test "member_of_group filter should return an empty set with = empty group" do
1571 1599 setup_member_of_group
1572 1600 @empty_group = Group.generate!
1573 1601 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
1574 1602
1575 1603 assert_equal [], find_issues_with_query(@query)
1576 1604 end
1577 1605
1578 1606 test "member_of_group filter should return issues with ! empty group" do
1579 1607 setup_member_of_group
1580 1608 @empty_group = Group.generate!
1581 1609 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
1582 1610
1583 1611 assert_find_issues_with_query_is_successful @query
1584 1612 end
1585 1613
1586 1614 def setup_assigned_to_role
1587 1615 @manager_role = Role.find_by_name('Manager')
1588 1616 @developer_role = Role.find_by_name('Developer')
1589 1617
1590 1618 @project = Project.generate!
1591 1619 @manager = User.generate!
1592 1620 @developer = User.generate!
1593 1621 @boss = User.generate!
1594 1622 @guest = User.generate!
1595 1623 User.add_to_project(@manager, @project, @manager_role)
1596 1624 User.add_to_project(@developer, @project, @developer_role)
1597 1625 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
1598 1626
1599 1627 @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id)
1600 1628 @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id)
1601 1629 @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id)
1602 1630 @issue4 = Issue.generate!(:project => @project, :assigned_to_id => @guest.id)
1603 1631 @issue5 = Issue.generate!(:project => @project)
1604 1632
1605 1633 @query = IssueQuery.new(:name => '_', :project => @project)
1606 1634 end
1607 1635
1608 1636 test "assigned_to_role filter should search assigned to for users with the Role" do
1609 1637 setup_assigned_to_role
1610 1638 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1611 1639
1612 1640 assert_query_result [@issue1, @issue3], @query
1613 1641 end
1614 1642
1615 1643 test "assigned_to_role filter should search assigned to for users with the Role on the issue project" do
1616 1644 setup_assigned_to_role
1617 1645 other_project = Project.generate!
1618 1646 User.add_to_project(@developer, other_project, @manager_role)
1619 1647 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1620 1648
1621 1649 assert_query_result [@issue1, @issue3], @query
1622 1650 end
1623 1651
1624 1652 test "assigned_to_role filter should return an empty set with empty role" do
1625 1653 setup_assigned_to_role
1626 1654 @empty_role = Role.generate!
1627 1655 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
1628 1656
1629 1657 assert_query_result [], @query
1630 1658 end
1631 1659
1632 1660 test "assigned_to_role filter should search assigned to for users without the Role" do
1633 1661 setup_assigned_to_role
1634 1662 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
1635 1663
1636 1664 assert_query_result [@issue2, @issue4, @issue5], @query
1637 1665 end
1638 1666
1639 1667 test "assigned_to_role filter should search assigned to for users not assigned to any Role (none)" do
1640 1668 setup_assigned_to_role
1641 1669 @query.add_filter('assigned_to_role', '!*', [''])
1642 1670
1643 1671 assert_query_result [@issue4, @issue5], @query
1644 1672 end
1645 1673
1646 1674 test "assigned_to_role filter should search assigned to for users assigned to any Role (all)" do
1647 1675 setup_assigned_to_role
1648 1676 @query.add_filter('assigned_to_role', '*', [''])
1649 1677
1650 1678 assert_query_result [@issue1, @issue2, @issue3], @query
1651 1679 end
1652 1680
1653 1681 test "assigned_to_role filter should return issues with ! empty role" do
1654 1682 setup_assigned_to_role
1655 1683 @empty_role = Role.generate!
1656 1684 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
1657 1685
1658 1686 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
1659 1687 end
1660 1688
1661 1689 def test_query_column_should_accept_a_symbol_as_caption
1662 1690 set_language_if_valid 'en'
1663 1691 c = QueryColumn.new('foo', :caption => :general_text_Yes)
1664 1692 assert_equal 'Yes', c.caption
1665 1693 end
1666 1694
1667 1695 def test_query_column_should_accept_a_proc_as_caption
1668 1696 c = QueryColumn.new('foo', :caption => lambda {'Foo'})
1669 1697 assert_equal 'Foo', c.caption
1670 1698 end
1671 1699 end
General Comments 0
You need to be logged in to leave comments. Login now