##// END OF EJS Templates
Ability to filter issues using project, author, assignee and target version custom fields (#8161)....
Jean-Philippe Lang -
r9981:3676783052fe
parent child
Show More

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

@@ -1,932 +1,962
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 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, :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.default_order = options[:default_order]
30 30 @caption_key = options[:caption] || "field_#{name}"
31 31 end
32 32
33 33 def caption
34 34 l(@caption_key)
35 35 end
36 36
37 37 # Returns true if the column is sortable, otherwise false
38 38 def sortable?
39 39 !@sortable.nil?
40 40 end
41 41
42 42 def sortable
43 43 @sortable.is_a?(Proc) ? @sortable.call : @sortable
44 44 end
45 45
46 46 def value(issue)
47 47 issue.send name
48 48 end
49 49
50 50 def css_classes
51 51 name
52 52 end
53 53 end
54 54
55 55 class QueryCustomFieldColumn < QueryColumn
56 56
57 57 def initialize(custom_field)
58 58 self.name = "cf_#{custom_field.id}".to_sym
59 59 self.sortable = custom_field.order_statement || false
60 60 self.groupable = custom_field.group_statement || false
61 61 @cf = custom_field
62 62 end
63 63
64 64 def caption
65 65 @cf.name
66 66 end
67 67
68 68 def custom_field
69 69 @cf
70 70 end
71 71
72 72 def value(issue)
73 73 cv = issue.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
74 74 cv.size > 1 ? cv : cv.first
75 75 end
76 76
77 77 def css_classes
78 78 @css_classes ||= "#{name} #{@cf.field_format}"
79 79 end
80 80 end
81 81
82 82 class Query < ActiveRecord::Base
83 83 class StatementInvalid < ::ActiveRecord::StatementInvalid
84 84 end
85 85
86 86 belongs_to :project
87 87 belongs_to :user
88 88 serialize :filters
89 89 serialize :column_names
90 90 serialize :sort_criteria, Array
91 91
92 92 attr_protected :project_id, :user_id
93 93
94 94 validates_presence_of :name
95 95 validates_length_of :name, :maximum => 255
96 96 validate :validate_query_filters
97 97
98 98 @@operators = { "=" => :label_equals,
99 99 "!" => :label_not_equals,
100 100 "o" => :label_open_issues,
101 101 "c" => :label_closed_issues,
102 102 "!*" => :label_none,
103 103 "*" => :label_all,
104 104 ">=" => :label_greater_or_equal,
105 105 "<=" => :label_less_or_equal,
106 106 "><" => :label_between,
107 107 "<t+" => :label_in_less_than,
108 108 ">t+" => :label_in_more_than,
109 109 "t+" => :label_in,
110 110 "t" => :label_today,
111 111 "w" => :label_this_week,
112 112 ">t-" => :label_less_than_ago,
113 113 "<t-" => :label_more_than_ago,
114 114 "t-" => :label_ago,
115 115 "~" => :label_contains,
116 116 "!~" => :label_not_contains }
117 117
118 118 cattr_reader :operators
119 119
120 120 @@operators_by_filter_type = { :list => [ "=", "!" ],
121 121 :list_status => [ "o", "=", "!", "c", "*" ],
122 122 :list_optional => [ "=", "!", "!*", "*" ],
123 123 :list_subprojects => [ "*", "!*", "=" ],
124 124 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-", "!*", "*" ],
125 125 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w", "!*", "*" ],
126 126 :string => [ "=", "~", "!", "!~", "!*", "*" ],
127 127 :text => [ "~", "!~", "!*", "*" ],
128 128 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
129 129 :float => [ "=", ">=", "<=", "><", "!*", "*" ] }
130 130
131 131 cattr_reader :operators_by_filter_type
132 132
133 133 @@available_columns = [
134 134 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
135 135 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
136 136 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
137 137 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
138 138 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
139 139 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
140 140 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
141 141 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
142 142 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
143 143 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
144 144 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
145 145 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
146 146 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
147 147 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
148 148 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
149 149 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
150 150 ]
151 151 cattr_reader :available_columns
152 152
153 153 scope :visible, lambda {|*args|
154 154 user = args.shift || User.current
155 155 base = Project.allowed_to_condition(user, :view_issues, *args)
156 156 user_id = user.logged? ? user.id : 0
157 157 {
158 158 :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id],
159 159 :include => :project
160 160 }
161 161 }
162 162
163 163 def initialize(attributes=nil, *args)
164 164 super attributes
165 165 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
166 166 @is_for_all = project.nil?
167 167 end
168 168
169 169 def validate_query_filters
170 170 filters.each_key do |field|
171 171 if values_for(field)
172 172 case type_for(field)
173 173 when :integer
174 174 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
175 175 when :float
176 176 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
177 177 when :date, :date_past
178 178 case operator_for(field)
179 179 when "=", ">=", "<=", "><"
180 180 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
181 181 when ">t-", "<t-", "t-"
182 182 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
183 183 end
184 184 end
185 185 end
186 186
187 187 add_filter_error(field, :blank) unless
188 188 # filter requires one or more values
189 189 (values_for(field) and !values_for(field).first.blank?) or
190 190 # filter doesn't require any value
191 191 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
192 192 end if filters
193 193 end
194 194
195 195 def add_filter_error(field, message)
196 196 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
197 197 errors.add(:base, m)
198 198 end
199 199
200 200 # Returns true if the query is visible to +user+ or the current user.
201 201 def visible?(user=User.current)
202 202 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
203 203 end
204 204
205 205 def editable_by?(user)
206 206 return false unless user
207 207 # Admin can edit them all and regular users can edit their private queries
208 208 return true if user.admin? || (!is_public && self.user_id == user.id)
209 209 # Members can not edit public queries that are for all project (only admin is allowed to)
210 210 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
211 211 end
212 212
213 213 def trackers
214 214 @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
215 215 end
216 216
217 217 # Returns a hash of localized labels for all filter operators
218 218 def self.operators_labels
219 219 operators.inject({}) {|h, operator| h[operator.first] = l(operator.last); h}
220 220 end
221 221
222 222 def available_filters
223 223 return @available_filters if @available_filters
224 224
225 225 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
226 226 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
227 227 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
228 228 "subject" => { :type => :text, :order => 8 },
229 229 "created_on" => { :type => :date_past, :order => 9 },
230 230 "updated_on" => { :type => :date_past, :order => 10 },
231 231 "start_date" => { :type => :date, :order => 11 },
232 232 "due_date" => { :type => :date, :order => 12 },
233 233 "estimated_hours" => { :type => :float, :order => 13 },
234 234 "done_ratio" => { :type => :integer, :order => 14 }}
235 235
236 236 principals = []
237 237 if project
238 238 principals += project.principals.sort
239 239 unless project.leaf?
240 240 subprojects = project.descendants.visible.all
241 241 if subprojects.any?
242 242 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
243 243 principals += Principal.member_of(subprojects)
244 244 end
245 245 end
246 246 else
247 247 all_projects = Project.visible.all
248 248 if all_projects.any?
249 249 # members of visible projects
250 250 principals += Principal.member_of(all_projects)
251 251
252 252 # project filter
253 253 project_values = []
254 254 if User.current.logged? && User.current.memberships.any?
255 255 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
256 256 end
257 257 Project.project_tree(all_projects) do |p, level|
258 258 prefix = (level > 0 ? ('--' * level + ' ') : '')
259 259 project_values << ["#{prefix}#{p.name}", p.id.to_s]
260 260 end
261 261 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
262 262 end
263 263 end
264 264 principals.uniq!
265 265 principals.sort!
266 266 users = principals.select {|p| p.is_a?(User)}
267 267
268 268 assigned_to_values = []
269 269 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
270 270 assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
271 271 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
272 272
273 273 author_values = []
274 274 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
275 275 author_values += users.collect{|s| [s.name, s.id.to_s] }
276 276 @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
277 277
278 278 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
279 279 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
280 280
281 281 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
282 282 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
283 283
284 284 if User.current.logged?
285 285 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
286 286 end
287 287
288 288 if project
289 289 # project specific filters
290 290 categories = project.issue_categories.all
291 291 unless categories.empty?
292 292 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
293 293 end
294 294 versions = project.shared_versions.all
295 295 unless versions.empty?
296 296 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
297 297 end
298 298 add_custom_fields_filters(project.all_issue_custom_fields)
299 299 else
300 300 # global filters for cross project issue list
301 301 system_shared_versions = Version.visible.find_all_by_sharing('system')
302 302 unless system_shared_versions.empty?
303 303 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
304 304 end
305 305 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
306 306 end
307 307
308 add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
309
308 310 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
309 311 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
310 312 @available_filters["is_private"] = { :type => :list, :order => 15, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] }
311 313 end
312 314
313 315 Tracker.disabled_core_fields(trackers).each {|field|
314 316 @available_filters.delete field
315 317 }
316 318
317 319 @available_filters.each do |field, options|
318 320 options[:name] ||= l("field_#{field}".gsub(/_id$/, ''))
319 321 end
320 322
321 323 @available_filters
322 324 end
323 325
324 326 # Returns a representation of the available filters for JSON serialization
325 327 def available_filters_as_json
326 328 json = {}
327 329 available_filters.each do |field, options|
328 330 json[field] = options.slice(:type, :name, :values).stringify_keys
329 331 end
330 332 json
331 333 end
332 334
333 335 def add_filter(field, operator, values)
334 336 # values must be an array
335 337 return unless values.nil? || values.is_a?(Array)
336 338 # check if field is defined as an available filter
337 339 if available_filters.has_key? field
338 340 filter_options = available_filters[field]
339 341 # check if operator is allowed for that filter
340 342 #if @@operators_by_filter_type[filter_options[:type]].include? operator
341 343 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
342 344 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
343 345 #end
344 346 filters[field] = {:operator => operator, :values => (values || [''])}
345 347 end
346 348 end
347 349
348 350 def add_short_filter(field, expression)
349 351 return unless expression && available_filters.has_key?(field)
350 352 field_type = available_filters[field][:type]
351 353 @@operators_by_filter_type[field_type].sort.reverse.detect do |operator|
352 354 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
353 355 add_filter field, operator, $1.present? ? $1.split('|') : ['']
354 356 end || add_filter(field, '=', expression.split('|'))
355 357 end
356 358
357 359 # Add multiple filters using +add_filter+
358 360 def add_filters(fields, operators, values)
359 361 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
360 362 fields.each do |field|
361 363 add_filter(field, operators[field], values && values[field])
362 364 end
363 365 end
364 366 end
365 367
366 368 def has_filter?(field)
367 369 filters and filters[field]
368 370 end
369 371
370 372 def type_for(field)
371 373 available_filters[field][:type] if available_filters.has_key?(field)
372 374 end
373 375
374 376 def operator_for(field)
375 377 has_filter?(field) ? filters[field][:operator] : nil
376 378 end
377 379
378 380 def values_for(field)
379 381 has_filter?(field) ? filters[field][:values] : nil
380 382 end
381 383
382 384 def value_for(field, index=0)
383 385 (values_for(field) || [])[index]
384 386 end
385 387
386 388 def label_for(field)
387 389 label = available_filters[field][:name] if available_filters.has_key?(field)
388 390 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
389 391 end
390 392
391 393 def available_columns
392 394 return @available_columns if @available_columns
393 395 @available_columns = ::Query.available_columns.dup
394 396 @available_columns += (project ?
395 397 project.all_issue_custom_fields :
396 398 IssueCustomField.find(:all)
397 399 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
398 400
399 401 if User.current.allowed_to?(:view_time_entries, project, :global => true)
400 402 index = nil
401 403 @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours}
402 404 index = (index ? index + 1 : -1)
403 405 # insert the column after estimated_hours or at the end
404 406 @available_columns.insert index, QueryColumn.new(:spent_hours,
405 407 :sortable => "(SELECT COALESCE(SUM(hours), 0) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id)",
406 408 :default_order => 'desc',
407 409 :caption => :label_spent_time
408 410 )
409 411 end
410 412
411 413 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
412 414 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
413 415 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
414 416 end
415 417
416 418 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
417 419 @available_columns.reject! {|column|
418 420 disabled_fields.include?(column.name.to_s)
419 421 }
420 422
421 423 @available_columns
422 424 end
423 425
424 426 def self.available_columns=(v)
425 427 self.available_columns = (v)
426 428 end
427 429
428 430 def self.add_available_column(column)
429 431 self.available_columns << (column) if column.is_a?(QueryColumn)
430 432 end
431 433
432 434 # Returns an array of columns that can be used to group the results
433 435 def groupable_columns
434 436 available_columns.select {|c| c.groupable}
435 437 end
436 438
437 439 # Returns a Hash of columns and the key for sorting
438 440 def sortable_columns
439 441 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
440 442 h[column.name.to_s] = column.sortable
441 443 h
442 444 })
443 445 end
444 446
445 447 def columns
446 448 # preserve the column_names order
447 449 (has_default_columns? ? default_columns_names : column_names).collect do |name|
448 450 available_columns.find { |col| col.name == name }
449 451 end.compact
450 452 end
451 453
452 454 def default_columns_names
453 455 @default_columns_names ||= begin
454 456 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
455 457
456 458 project.present? ? default_columns : [:project] | default_columns
457 459 end
458 460 end
459 461
460 462 def column_names=(names)
461 463 if names
462 464 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
463 465 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
464 466 # Set column_names to nil if default columns
465 467 if names == default_columns_names
466 468 names = nil
467 469 end
468 470 end
469 471 write_attribute(:column_names, names)
470 472 end
471 473
472 474 def has_column?(column)
473 475 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
474 476 end
475 477
476 478 def has_default_columns?
477 479 column_names.nil? || column_names.empty?
478 480 end
479 481
480 482 def sort_criteria=(arg)
481 483 c = []
482 484 if arg.is_a?(Hash)
483 485 arg = arg.keys.sort.collect {|k| arg[k]}
484 486 end
485 487 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
486 488 write_attribute(:sort_criteria, c)
487 489 end
488 490
489 491 def sort_criteria
490 492 read_attribute(:sort_criteria) || []
491 493 end
492 494
493 495 def sort_criteria_key(arg)
494 496 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
495 497 end
496 498
497 499 def sort_criteria_order(arg)
498 500 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
499 501 end
500 502
501 503 # Returns the SQL sort order that should be prepended for grouping
502 504 def group_by_sort_order
503 505 if grouped? && (column = group_by_column)
504 506 column.sortable.is_a?(Array) ?
505 507 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
506 508 "#{column.sortable} #{column.default_order}"
507 509 end
508 510 end
509 511
510 512 # Returns true if the query is a grouped query
511 513 def grouped?
512 514 !group_by_column.nil?
513 515 end
514 516
515 517 def group_by_column
516 518 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
517 519 end
518 520
519 521 def group_by_statement
520 522 group_by_column.try(:groupable)
521 523 end
522 524
523 525 def project_statement
524 526 project_clauses = []
525 527 if project && !project.descendants.active.empty?
526 528 ids = [project.id]
527 529 if has_filter?("subproject_id")
528 530 case operator_for("subproject_id")
529 531 when '='
530 532 # include the selected subprojects
531 533 ids += values_for("subproject_id").each(&:to_i)
532 534 when '!*'
533 535 # main project only
534 536 else
535 537 # all subprojects
536 538 ids += project.descendants.collect(&:id)
537 539 end
538 540 elsif Setting.display_subprojects_issues?
539 541 ids += project.descendants.collect(&:id)
540 542 end
541 543 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
542 544 elsif project
543 545 project_clauses << "#{Project.table_name}.id = %d" % project.id
544 546 end
545 547 project_clauses.any? ? project_clauses.join(' AND ') : nil
546 548 end
547 549
548 550 def statement
549 551 # filters clauses
550 552 filters_clauses = []
551 553 filters.each_key do |field|
552 554 next if field == "subproject_id"
553 555 v = values_for(field).clone
554 556 next unless v and !v.empty?
555 557 operator = operator_for(field)
556 558
557 559 # "me" value subsitution
558 560 if %w(assigned_to_id author_id watcher_id).include?(field)
559 561 if v.delete("me")
560 562 if User.current.logged?
561 563 v.push(User.current.id.to_s)
562 564 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
563 565 else
564 566 v.push("0")
565 567 end
566 568 end
567 569 end
568 570
569 571 if field == 'project_id'
570 572 if v.delete('mine')
571 573 v += User.current.memberships.map(&:project_id).map(&:to_s)
572 574 end
573 575 end
574 576
575 if field =~ /^cf_(\d+)$/
577 if field =~ /cf_(\d+)$/
576 578 # custom field
577 579 filters_clauses << sql_for_custom_field(field, operator, v, $1)
578 580 elsif respond_to?("sql_for_#{field}_field")
579 581 # specific statement
580 582 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
581 583 else
582 584 # regular field
583 585 filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')'
584 586 end
585 587 end if filters and valid?
586 588
587 589 filters_clauses << project_statement
588 590 filters_clauses.reject!(&:blank?)
589 591
590 592 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
591 593 end
592 594
593 595 # Returns the issue count
594 596 def issue_count
595 597 Issue.visible.count(:include => [:status, :project], :conditions => statement)
596 598 rescue ::ActiveRecord::StatementInvalid => e
597 599 raise StatementInvalid.new(e.message)
598 600 end
599 601
600 602 # Returns the issue count by group or nil if query is not grouped
601 603 def issue_count_by_group
602 604 r = nil
603 605 if grouped?
604 606 begin
605 607 # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value
606 608 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
607 609 rescue ActiveRecord::RecordNotFound
608 610 r = {nil => issue_count}
609 611 end
610 612 c = group_by_column
611 613 if c.is_a?(QueryCustomFieldColumn)
612 614 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
613 615 end
614 616 end
615 617 r
616 618 rescue ::ActiveRecord::StatementInvalid => e
617 619 raise StatementInvalid.new(e.message)
618 620 end
619 621
620 622 # Returns the issues
621 623 # Valid options are :order, :offset, :limit, :include, :conditions
622 624 def issues(options={})
623 625 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
624 626 order_option = nil if order_option.blank?
625 627
626 628 issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
627 629 :conditions => statement,
628 630 :order => order_option,
629 631 :joins => joins_for_order_statement(order_option),
630 632 :limit => options[:limit],
631 633 :offset => options[:offset]
632 634
633 635 if has_column?(:spent_hours)
634 636 Issue.load_visible_spent_hours(issues)
635 637 end
636 638 issues
637 639 rescue ::ActiveRecord::StatementInvalid => e
638 640 raise StatementInvalid.new(e.message)
639 641 end
640 642
641 643 # Returns the issues ids
642 644 def issue_ids(options={})
643 645 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
644 646 order_option = nil if order_option.blank?
645 647
646 648 Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq,
647 649 :conditions => statement,
648 650 :order => order_option,
649 651 :joins => joins_for_order_statement(order_option),
650 652 :limit => options[:limit],
651 653 :offset => options[:offset]).find_ids
652 654 rescue ::ActiveRecord::StatementInvalid => e
653 655 raise StatementInvalid.new(e.message)
654 656 end
655 657
656 658 # Returns the journals
657 659 # Valid options are :order, :offset, :limit
658 660 def journals(options={})
659 661 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
660 662 :conditions => statement,
661 663 :order => options[:order],
662 664 :limit => options[:limit],
663 665 :offset => options[:offset]
664 666 rescue ::ActiveRecord::StatementInvalid => e
665 667 raise StatementInvalid.new(e.message)
666 668 end
667 669
668 670 # Returns the versions
669 671 # Valid options are :conditions
670 672 def versions(options={})
671 673 Version.visible.scoped(:conditions => options[:conditions]).find :all, :include => :project, :conditions => project_statement
672 674 rescue ::ActiveRecord::StatementInvalid => e
673 675 raise StatementInvalid.new(e.message)
674 676 end
675 677
676 678 def sql_for_watcher_id_field(field, operator, value)
677 679 db_table = Watcher.table_name
678 680 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
679 681 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
680 682 end
681 683
682 684 def sql_for_member_of_group_field(field, operator, value)
683 685 if operator == '*' # Any group
684 686 groups = Group.all
685 687 operator = '=' # Override the operator since we want to find by assigned_to
686 688 elsif operator == "!*"
687 689 groups = Group.all
688 690 operator = '!' # Override the operator since we want to find by assigned_to
689 691 else
690 692 groups = Group.find_all_by_id(value)
691 693 end
692 694 groups ||= []
693 695
694 696 members_of_groups = groups.inject([]) {|user_ids, group|
695 697 if group && group.user_ids.present?
696 698 user_ids << group.user_ids
697 699 end
698 700 user_ids.flatten.uniq.compact
699 701 }.sort.collect(&:to_s)
700 702
701 703 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
702 704 end
703 705
704 706 def sql_for_assigned_to_role_field(field, operator, value)
705 707 case operator
706 708 when "*", "!*" # Member / Not member
707 709 sw = operator == "!*" ? 'NOT' : ''
708 710 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
709 711 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
710 712 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
711 713 when "=", "!"
712 714 role_cond = value.any? ?
713 715 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" :
714 716 "1=0"
715 717
716 718 sw = operator == "!" ? 'NOT' : ''
717 719 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
718 720 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
719 721 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
720 722 end
721 723 end
722 724
723 725 def sql_for_is_private_field(field, operator, value)
724 726 op = (operator == "=" ? 'IN' : 'NOT IN')
725 727 va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',')
726 728
727 729 "#{Issue.table_name}.is_private #{op} (#{va})"
728 730 end
729 731
730 732 private
731 733
732 734 def sql_for_custom_field(field, operator, value, custom_field_id)
733 735 db_table = CustomValue.table_name
734 736 db_field = 'value'
735 737 filter = @available_filters[field]
736 if filter && filter[:format] == 'user'
738 return nil unless filter
739 if filter[:format] == 'user'
737 740 if value.delete('me')
738 741 value.push User.current.id.to_s
739 742 end
740 743 end
741 744 not_in = nil
742 745 if operator == '!'
743 746 # Makes ! operator work for custom fields with multiple values
744 747 operator = '='
745 748 not_in = 'NOT'
746 749 end
747 "#{Issue.table_name}.id #{not_in} IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " +
750 customized_key = "id"
751 customized_class = Issue
752 if field =~ /^(.+)\.cf_/
753 assoc = $1
754 customized_key = "#{assoc}_id"
755 customized_class = Issue.reflect_on_association(assoc.to_sym).klass.base_class rescue nil
756 raise "Unknown Issue association #{assoc}" unless customized_class
757 end
758 "#{Issue.table_name}.#{customized_key} #{not_in} IN (SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name} 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} WHERE " +
748 759 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
749 760 end
750 761
751 762 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
752 763 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
753 764 sql = ''
754 765 case operator
755 766 when "="
756 767 if value.any?
757 768 case type_for(field)
758 769 when :date, :date_past
759 770 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
760 771 when :integer
761 772 if is_custom_filter
762 773 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) = #{value.first.to_i})"
763 774 else
764 775 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
765 776 end
766 777 when :float
767 778 if is_custom_filter
768 779 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})"
769 780 else
770 781 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
771 782 end
772 783 else
773 784 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
774 785 end
775 786 else
776 787 # IN an empty set
777 788 sql = "1=0"
778 789 end
779 790 when "!"
780 791 if value.any?
781 792 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
782 793 else
783 794 # NOT IN an empty set
784 795 sql = "1=1"
785 796 end
786 797 when "!*"
787 798 sql = "#{db_table}.#{db_field} IS NULL"
788 799 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
789 800 when "*"
790 801 sql = "#{db_table}.#{db_field} IS NOT NULL"
791 802 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
792 803 when ">="
793 804 if [:date, :date_past].include?(type_for(field))
794 805 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
795 806 else
796 807 if is_custom_filter
797 808 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f})"
798 809 else
799 810 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
800 811 end
801 812 end
802 813 when "<="
803 814 if [:date, :date_past].include?(type_for(field))
804 815 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
805 816 else
806 817 if is_custom_filter
807 818 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f})"
808 819 else
809 820 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
810 821 end
811 822 end
812 823 when "><"
813 824 if [:date, :date_past].include?(type_for(field))
814 825 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
815 826 else
816 827 if is_custom_filter
817 828 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})"
818 829 else
819 830 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
820 831 end
821 832 end
822 833 when "o"
823 834 sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id"
824 835 when "c"
825 836 sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id"
826 837 when ">t-"
827 838 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
828 839 when "<t-"
829 840 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
830 841 when "t-"
831 842 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
832 843 when ">t+"
833 844 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
834 845 when "<t+"
835 846 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
836 847 when "t+"
837 848 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
838 849 when "t"
839 850 sql = relative_date_clause(db_table, db_field, 0, 0)
840 851 when "w"
841 852 first_day_of_week = l(:general_first_day_of_week).to_i
842 853 day_of_week = Date.today.cwday
843 854 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
844 855 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
845 856 when "~"
846 857 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
847 858 when "!~"
848 859 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
849 860 else
850 861 raise "Unknown query operator #{operator}"
851 862 end
852 863
853 864 return sql
854 865 end
855 866
856 def add_custom_fields_filters(custom_fields)
867 def add_custom_fields_filters(custom_fields, assoc=nil)
868 return unless custom_fields.present?
857 869 @available_filters ||= {}
858 870
859 871 custom_fields.select(&:is_filter?).each do |field|
860 872 case field.field_format
861 873 when "text"
862 874 options = { :type => :text, :order => 20 }
863 875 when "list"
864 876 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
865 877 when "date"
866 878 options = { :type => :date, :order => 20 }
867 879 when "bool"
868 880 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
869 881 when "int"
870 882 options = { :type => :integer, :order => 20 }
871 883 when "float"
872 884 options = { :type => :float, :order => 20 }
873 885 when "user", "version"
874 886 next unless project
875 887 values = field.possible_values_options(project)
876 888 if User.current.logged? && field.field_format == 'user'
877 889 values.unshift ["<< #{l(:label_me)} >>", "me"]
878 890 end
879 891 options = { :type => :list_optional, :values => values, :order => 20}
880 892 else
881 893 options = { :type => :string, :order => 20 }
882 894 end
883 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name, :format => field.field_format })
895 filter_id = "cf_#{field.id}"
896 filter_name = field.name
897 if assoc.present?
898 filter_id = "#{assoc}.#{filter_id}"
899 filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
900 end
901 @available_filters[filter_id] = options.merge({ :name => filter_name, :format => field.field_format })
902 end
903 end
904
905 def add_associations_custom_fields_filters(*associations)
906 fields_by_class = CustomField.where(:is_filter => true).group_by(&:class)
907 associations.each do |assoc|
908 association_klass = Issue.reflect_on_association(assoc).klass
909 fields_by_class.each do |field_class, fields|
910 if field_class.customized_class <= association_klass
911 add_custom_fields_filters(fields, assoc)
912 end
913 end
884 914 end
885 915 end
886 916
887 917 # Returns a SQL clause for a date or datetime field.
888 918 def date_clause(table, field, from, to)
889 919 s = []
890 920 if from
891 921 from_yesterday = from - 1
892 922 from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
893 923 if self.class.default_timezone == :utc
894 924 from_yesterday_time = from_yesterday_time.utc
895 925 end
896 926 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)])
897 927 end
898 928 if to
899 929 to_time = Time.local(to.year, to.month, to.day)
900 930 if self.class.default_timezone == :utc
901 931 to_time = to_time.utc
902 932 end
903 933 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
904 934 end
905 935 s.join(' AND ')
906 936 end
907 937
908 938 # Returns a SQL clause for a date or datetime field using relative dates.
909 939 def relative_date_clause(table, field, days_from, days_to)
910 940 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
911 941 end
912 942
913 943 # Additional joins required for the given sort options
914 944 def joins_for_order_statement(order_options)
915 945 joins = []
916 946
917 947 if order_options
918 948 if order_options.include?('authors')
919 949 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{Issue.table_name}.author_id"
920 950 end
921 951 order_options.scan(/cf_\d+/).uniq.each do |name|
922 952 column = available_columns.detect {|c| c.name.to_s == name}
923 953 join = column && column.custom_field.join_for_order_statement
924 954 if join
925 955 joins << join
926 956 end
927 957 end
928 958 end
929 959
930 960 joins.any? ? joins.join(' ') : nil
931 961 end
932 962 end
@@ -1,72 +1,82
1 1 <%= error_messages_for 'custom_field' %>
2 2
3 3 <div class="box tabular">
4 4 <p><%= f.text_field :name, :required => true %></p>
5 5 <p><%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :disabled => !@custom_field.new_record? %></p>
6 6
7 7 <% if @custom_field.format_in? 'list', 'user', 'version' %>
8 8 <p><%= f.check_box :multiple, :disabled => @custom_field.multiple && !@custom_field.new_record? %></p>
9 9 <% end %>
10 10
11 11 <% unless @custom_field.format_in? 'list', 'bool', 'date', 'user', 'version' %>
12 12 <p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
13 13 <%= f.text_field :min_length, :size => 5, :no_label => true %> -
14 14 <%= f.text_field :max_length, :size => 5, :no_label => true %><br />(<%=l(:text_min_max_length_info)%>)</p>
15 15 <p><%= f.text_field :regexp, :size => 50 %><br />(<%=l(:text_regexp_info)%>)</p>
16 16 <% end %>
17 17
18 18 <% if @custom_field.format_in? 'list' %>
19 19 <p>
20 20 <%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %>
21 21 <em class="info"><%= l(:text_custom_field_possible_values_info) %></em>
22 22 </p>
23 23 <% end %>
24 24
25 25 <% unless @custom_field.format_in? 'user', 'version' %>
26 26 <p><%= @custom_field.field_format == 'bool' ? f.check_box(:default_value) : f.text_field(:default_value) %></p>
27 27 <% end %>
28 28
29 29 <%= call_hook(:view_custom_fields_form_upper_box, :custom_field => @custom_field, :form => f) %>
30 30 </div>
31 31
32 32 <div class="box tabular">
33 33 <% case @custom_field.class.name
34 34 when "IssueCustomField" %>
35 35
36 36 <fieldset><legend><%=l(:label_tracker_plural)%></legend>
37 37 <% Tracker.sorted.all.each do |tracker| %>
38 38 <%= check_box_tag "custom_field[tracker_ids][]",
39 39 tracker.id,
40 40 (@custom_field.trackers.include? tracker),
41 41 :id => "custom_field_tracker_ids_#{tracker.id}" %>
42 42 <label class="no-css" for="custom_field_tracker_ids_<%=tracker.id%>">
43 43 <%= h(tracker.name) %>
44 44 </label>
45 45 <% end %>
46 46 <%= hidden_field_tag "custom_field[tracker_ids][]", '' %>
47 47 </fieldset>
48 48 &nbsp;
49 49 <p><%= f.check_box :is_required %></p>
50 50 <p><%= f.check_box :is_for_all %></p>
51 51 <p><%= f.check_box :is_filter %></p>
52 52 <p><%= f.check_box :searchable %></p>
53 53
54 54 <% when "UserCustomField" %>
55 55 <p><%= f.check_box :is_required %></p>
56 56 <p><%= f.check_box :visible %></p>
57 57 <p><%= f.check_box :editable %></p>
58 <p><%= f.check_box :is_filter %></p>
58 59
59 60 <% when "ProjectCustomField" %>
60 61 <p><%= f.check_box :is_required %></p>
61 62 <p><%= f.check_box :visible %></p>
62 63 <p><%= f.check_box :searchable %></p>
64 <p><%= f.check_box :is_filter %></p>
65
66 <% when "VersionCustomField" %>
67 <p><%= f.check_box :is_required %></p>
68 <p><%= f.check_box :is_filter %></p>
69
70 <% when "GroupCustomField" %>
71 <p><%= f.check_box :is_required %></p>
72 <p><%= f.check_box :is_filter %></p>
63 73
64 74 <% when "TimeEntryCustomField" %>
65 75 <p><%= f.check_box :is_required %></p>
66 76
67 77 <% else %>
68 78 <p><%= f.check_box :is_required %></p>
69 79
70 80 <% end %>
71 81 <%= call_hook(:"view_custom_fields_form_#{@custom_field.type.to_s.underscore}", :custom_field => @custom_field, :form => f) %>
72 82 </div>
@@ -1,1057 +1,1061
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: "can't be empty"
114 114 blank: "can't 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
133 133 actionview_instancetag_blank_option: Please select
134 134
135 135 general_text_No: 'No'
136 136 general_text_Yes: 'Yes'
137 137 general_text_no: 'no'
138 138 general_text_yes: 'yes'
139 139 general_lang_name: 'English'
140 140 general_csv_separator: ','
141 141 general_csv_decimal_separator: '.'
142 142 general_csv_encoding: ISO-8859-1
143 143 general_pdf_encoding: UTF-8
144 144 general_first_day_of_week: '7'
145 145
146 146 notice_account_updated: Account was successfully updated.
147 147 notice_account_invalid_creditentials: Invalid user or password
148 148 notice_account_password_updated: Password was successfully updated.
149 149 notice_account_wrong_password: Wrong password
150 150 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
151 151 notice_account_unknown_email: Unknown user.
152 152 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
153 153 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
154 154 notice_account_activated: Your account has been activated. You can now log in.
155 155 notice_successful_create: Successful creation.
156 156 notice_successful_update: Successful update.
157 157 notice_successful_delete: Successful deletion.
158 158 notice_successful_connection: Successful connection.
159 159 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
160 160 notice_locking_conflict: Data has been updated by another user.
161 161 notice_not_authorized: You are not authorized to access this page.
162 162 notice_not_authorized_archived_project: The project you're trying to access has been archived.
163 163 notice_email_sent: "An email was sent to %{value}"
164 164 notice_email_error: "An error occurred while sending mail (%{value})"
165 165 notice_feeds_access_key_reseted: Your RSS access key was reset.
166 166 notice_api_access_key_reseted: Your API access key was reset.
167 167 notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}."
168 168 notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
169 169 notice_failed_to_save_members: "Failed to save member(s): %{errors}."
170 170 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
171 171 notice_account_pending: "Your account was created and is now pending administrator approval."
172 172 notice_default_data_loaded: Default configuration successfully loaded.
173 173 notice_unable_delete_version: Unable to delete version.
174 174 notice_unable_delete_time_entry: Unable to delete time log entry.
175 175 notice_issue_done_ratios_updated: Issue done ratios updated.
176 176 notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
177 177 notice_issue_successful_create: "Issue %{id} created."
178 178 notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it."
179 179 notice_account_deleted: "Your account has been permanently deleted."
180 180 notice_user_successful_create: "User %{id} created."
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 error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})"
201 201 error_session_expired: "Your session has expired. Please login again."
202 202 warning_attachments_not_saved: "%{count} file(s) could not be saved."
203 203
204 204 mail_subject_lost_password: "Your %{value} password"
205 205 mail_body_lost_password: 'To change your password, click on the following link:'
206 206 mail_subject_register: "Your %{value} account activation"
207 207 mail_body_register: 'To activate your account, click on the following link:'
208 208 mail_body_account_information_external: "You can use your %{value} account to log in."
209 209 mail_body_account_information: Your account information
210 210 mail_subject_account_activation_request: "%{value} account activation request"
211 211 mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:"
212 212 mail_subject_reminder: "%{count} issue(s) due in the next %{days} days"
213 213 mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:"
214 214 mail_subject_wiki_content_added: "'%{id}' wiki page has been added"
215 215 mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}."
216 216 mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated"
217 217 mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}."
218 218
219 219 gui_validation_error: 1 error
220 220 gui_validation_error_plural: "%{count} errors"
221 221
222 222 field_name: Name
223 223 field_description: Description
224 224 field_summary: Summary
225 225 field_is_required: Required
226 226 field_firstname: First name
227 227 field_lastname: Last name
228 228 field_mail: Email
229 229 field_filename: File
230 230 field_filesize: Size
231 231 field_downloads: Downloads
232 232 field_author: Author
233 233 field_created_on: Created
234 234 field_updated_on: Updated
235 235 field_field_format: Format
236 236 field_is_for_all: For all projects
237 237 field_possible_values: Possible values
238 238 field_regexp: Regular expression
239 239 field_min_length: Minimum length
240 240 field_max_length: Maximum length
241 241 field_value: Value
242 242 field_category: Category
243 243 field_title: Title
244 244 field_project: Project
245 245 field_issue: Issue
246 246 field_status: Status
247 247 field_notes: Notes
248 248 field_is_closed: Issue closed
249 249 field_is_default: Default value
250 250 field_tracker: Tracker
251 251 field_subject: Subject
252 252 field_due_date: Due date
253 253 field_assigned_to: Assignee
254 254 field_priority: Priority
255 255 field_fixed_version: Target version
256 256 field_user: User
257 257 field_principal: Principal
258 258 field_role: Role
259 259 field_homepage: Homepage
260 260 field_is_public: Public
261 261 field_parent: Subproject of
262 262 field_is_in_roadmap: Issues displayed in roadmap
263 263 field_login: Login
264 264 field_mail_notification: Email notifications
265 265 field_admin: Administrator
266 266 field_last_login_on: Last connection
267 267 field_language: Language
268 268 field_effective_date: Date
269 269 field_password: Password
270 270 field_new_password: New password
271 271 field_password_confirmation: Confirmation
272 272 field_version: Version
273 273 field_type: Type
274 274 field_host: Host
275 275 field_port: Port
276 276 field_account: Account
277 277 field_base_dn: Base DN
278 278 field_attr_login: Login attribute
279 279 field_attr_firstname: Firstname attribute
280 280 field_attr_lastname: Lastname attribute
281 281 field_attr_mail: Email attribute
282 282 field_onthefly: On-the-fly user creation
283 283 field_start_date: Start date
284 284 field_done_ratio: "% Done"
285 285 field_auth_source: Authentication mode
286 286 field_hide_mail: Hide my email address
287 287 field_comments: Comment
288 288 field_url: URL
289 289 field_start_page: Start page
290 290 field_subproject: Subproject
291 291 field_hours: Hours
292 292 field_activity: Activity
293 293 field_spent_on: Date
294 294 field_identifier: Identifier
295 295 field_is_filter: Used as a filter
296 296 field_issue_to: Related issue
297 297 field_delay: Delay
298 298 field_assignable: Issues can be assigned to this role
299 299 field_redirect_existing_links: Redirect existing links
300 300 field_estimated_hours: Estimated time
301 301 field_column_names: Columns
302 302 field_time_entries: Log time
303 303 field_time_zone: Time zone
304 304 field_searchable: Searchable
305 305 field_default_value: Default value
306 306 field_comments_sorting: Display comments
307 307 field_parent_title: Parent page
308 308 field_editable: Editable
309 309 field_watcher: Watcher
310 310 field_identity_url: OpenID URL
311 311 field_content: Content
312 312 field_group_by: Group results by
313 313 field_sharing: Sharing
314 314 field_parent_issue: Parent task
315 315 field_member_of_group: "Assignee's group"
316 316 field_assigned_to_role: "Assignee's role"
317 317 field_text: Text field
318 318 field_visible: Visible
319 319 field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
320 320 field_issues_visibility: Issues visibility
321 321 field_is_private: Private
322 322 field_commit_logs_encoding: Commit messages encoding
323 323 field_scm_path_encoding: Path encoding
324 324 field_path_to_repository: Path to repository
325 325 field_root_directory: Root directory
326 326 field_cvsroot: CVSROOT
327 327 field_cvs_module: Module
328 328 field_repository_is_default: Main repository
329 329 field_multiple: Multiple values
330 330 field_auth_source_ldap_filter: LDAP filter
331 331 field_core_fields: Standard fields
332 332 field_timeout: "Timeout (in seconds)"
333 333 field_board_parent: Parent forum
334 334
335 335 setting_app_title: Application title
336 336 setting_app_subtitle: Application subtitle
337 337 setting_welcome_text: Welcome text
338 338 setting_default_language: Default language
339 339 setting_login_required: Authentication required
340 340 setting_self_registration: Self-registration
341 341 setting_attachment_max_size: Maximum attachment size
342 342 setting_issues_export_limit: Issues export limit
343 343 setting_mail_from: Emission email address
344 344 setting_bcc_recipients: Blind carbon copy recipients (bcc)
345 345 setting_plain_text_mail: Plain text mail (no HTML)
346 346 setting_host_name: Host name and path
347 347 setting_text_formatting: Text formatting
348 348 setting_wiki_compression: Wiki history compression
349 349 setting_feeds_limit: Maximum number of items in Atom feeds
350 350 setting_default_projects_public: New projects are public by default
351 351 setting_autofetch_changesets: Fetch commits automatically
352 352 setting_sys_api_enabled: Enable WS for repository management
353 353 setting_commit_ref_keywords: Referencing keywords
354 354 setting_commit_fix_keywords: Fixing keywords
355 355 setting_autologin: Autologin
356 356 setting_date_format: Date format
357 357 setting_time_format: Time format
358 358 setting_cross_project_issue_relations: Allow cross-project issue relations
359 359 setting_issue_list_default_columns: Default columns displayed on the issue list
360 360 setting_repositories_encodings: Attachments and repositories encodings
361 361 setting_emails_header: Emails header
362 362 setting_emails_footer: Emails footer
363 363 setting_protocol: Protocol
364 364 setting_per_page_options: Objects per page options
365 365 setting_user_format: Users display format
366 366 setting_activity_days_default: Days displayed on project activity
367 367 setting_display_subprojects_issues: Display subprojects issues on main projects by default
368 368 setting_enabled_scm: Enabled SCM
369 369 setting_mail_handler_body_delimiters: "Truncate emails after one of these lines"
370 370 setting_mail_handler_api_enabled: Enable WS for incoming emails
371 371 setting_mail_handler_api_key: API key
372 372 setting_sequential_project_identifiers: Generate sequential project identifiers
373 373 setting_gravatar_enabled: Use Gravatar user icons
374 374 setting_gravatar_default: Default Gravatar image
375 375 setting_diff_max_lines_displayed: Maximum number of diff lines displayed
376 376 setting_file_max_size_displayed: Maximum size of text files displayed inline
377 377 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
378 378 setting_openid: Allow OpenID login and registration
379 379 setting_password_min_length: Minimum password length
380 380 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
381 381 setting_default_projects_modules: Default enabled modules for new projects
382 382 setting_issue_done_ratio: Calculate the issue done ratio with
383 383 setting_issue_done_ratio_issue_field: Use the issue field
384 384 setting_issue_done_ratio_issue_status: Use the issue status
385 385 setting_start_of_week: Start calendars on
386 386 setting_rest_api_enabled: Enable REST web service
387 387 setting_cache_formatted_text: Cache formatted text
388 388 setting_default_notification_option: Default notification option
389 389 setting_commit_logtime_enabled: Enable time logging
390 390 setting_commit_logtime_activity_id: Activity for logged time
391 391 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
392 392 setting_issue_group_assignment: Allow issue assignment to groups
393 393 setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
394 394 setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed
395 395 setting_unsubscribe: Allow users to delete their own account
396 396 setting_session_lifetime: Session maximum lifetime
397 397 setting_session_timeout: Session inactivity timeout
398 398 setting_thumbnails_enabled: Display attachment thumbnails
399 399 setting_thumbnails_size: Thumbnails size (in pixels)
400 400
401 401 permission_add_project: Create project
402 402 permission_add_subprojects: Create subprojects
403 403 permission_edit_project: Edit project
404 404 permission_close_project: Close / reopen the project
405 405 permission_select_project_modules: Select project modules
406 406 permission_manage_members: Manage members
407 407 permission_manage_project_activities: Manage project activities
408 408 permission_manage_versions: Manage versions
409 409 permission_manage_categories: Manage issue categories
410 410 permission_view_issues: View Issues
411 411 permission_add_issues: Add issues
412 412 permission_edit_issues: Edit issues
413 413 permission_manage_issue_relations: Manage issue relations
414 414 permission_set_issues_private: Set issues public or private
415 415 permission_set_own_issues_private: Set own issues public or private
416 416 permission_add_issue_notes: Add notes
417 417 permission_edit_issue_notes: Edit notes
418 418 permission_edit_own_issue_notes: Edit own notes
419 419 permission_move_issues: Move issues
420 420 permission_delete_issues: Delete issues
421 421 permission_manage_public_queries: Manage public queries
422 422 permission_save_queries: Save queries
423 423 permission_view_gantt: View gantt chart
424 424 permission_view_calendar: View calendar
425 425 permission_view_issue_watchers: View watchers list
426 426 permission_add_issue_watchers: Add watchers
427 427 permission_delete_issue_watchers: Delete watchers
428 428 permission_log_time: Log spent time
429 429 permission_view_time_entries: View spent time
430 430 permission_edit_time_entries: Edit time logs
431 431 permission_edit_own_time_entries: Edit own time logs
432 432 permission_manage_news: Manage news
433 433 permission_comment_news: Comment news
434 434 permission_manage_documents: Manage documents
435 435 permission_view_documents: View documents
436 436 permission_manage_files: Manage files
437 437 permission_view_files: View files
438 438 permission_manage_wiki: Manage wiki
439 439 permission_rename_wiki_pages: Rename wiki pages
440 440 permission_delete_wiki_pages: Delete wiki pages
441 441 permission_view_wiki_pages: View wiki
442 442 permission_view_wiki_edits: View wiki history
443 443 permission_edit_wiki_pages: Edit wiki pages
444 444 permission_delete_wiki_pages_attachments: Delete attachments
445 445 permission_protect_wiki_pages: Protect wiki pages
446 446 permission_manage_repository: Manage repository
447 447 permission_browse_repository: Browse repository
448 448 permission_view_changesets: View changesets
449 449 permission_commit_access: Commit access
450 450 permission_manage_boards: Manage forums
451 451 permission_view_messages: View messages
452 452 permission_add_messages: Post messages
453 453 permission_edit_messages: Edit messages
454 454 permission_edit_own_messages: Edit own messages
455 455 permission_delete_messages: Delete messages
456 456 permission_delete_own_messages: Delete own messages
457 457 permission_export_wiki_pages: Export wiki pages
458 458 permission_manage_subtasks: Manage subtasks
459 459 permission_manage_related_issues: Manage related issues
460 460
461 461 project_module_issue_tracking: Issue tracking
462 462 project_module_time_tracking: Time tracking
463 463 project_module_news: News
464 464 project_module_documents: Documents
465 465 project_module_files: Files
466 466 project_module_wiki: Wiki
467 467 project_module_repository: Repository
468 468 project_module_boards: Forums
469 469 project_module_calendar: Calendar
470 470 project_module_gantt: Gantt
471 471
472 472 label_user: User
473 473 label_user_plural: Users
474 474 label_user_new: New user
475 475 label_user_anonymous: Anonymous
476 476 label_project: Project
477 477 label_project_new: New project
478 478 label_project_plural: Projects
479 479 label_x_projects:
480 480 zero: no projects
481 481 one: 1 project
482 482 other: "%{count} projects"
483 483 label_project_all: All Projects
484 484 label_project_latest: Latest projects
485 485 label_issue: Issue
486 486 label_issue_new: New issue
487 487 label_issue_plural: Issues
488 488 label_issue_view_all: View all issues
489 489 label_issues_by: "Issues by %{value}"
490 490 label_issue_added: Issue added
491 491 label_issue_updated: Issue updated
492 492 label_issue_note_added: Note added
493 493 label_issue_status_updated: Status updated
494 494 label_issue_priority_updated: Priority updated
495 495 label_document: Document
496 496 label_document_new: New document
497 497 label_document_plural: Documents
498 498 label_document_added: Document added
499 499 label_role: Role
500 500 label_role_plural: Roles
501 501 label_role_new: New role
502 502 label_role_and_permissions: Roles and permissions
503 503 label_role_anonymous: Anonymous
504 504 label_role_non_member: Non member
505 505 label_member: Member
506 506 label_member_new: New member
507 507 label_member_plural: Members
508 508 label_tracker: Tracker
509 509 label_tracker_plural: Trackers
510 510 label_tracker_new: New tracker
511 511 label_workflow: Workflow
512 512 label_issue_status: Issue status
513 513 label_issue_status_plural: Issue statuses
514 514 label_issue_status_new: New status
515 515 label_issue_category: Issue category
516 516 label_issue_category_plural: Issue categories
517 517 label_issue_category_new: New category
518 518 label_custom_field: Custom field
519 519 label_custom_field_plural: Custom fields
520 520 label_custom_field_new: New custom field
521 521 label_enumerations: Enumerations
522 522 label_enumeration_new: New value
523 523 label_information: Information
524 524 label_information_plural: Information
525 525 label_please_login: Please log in
526 526 label_register: Register
527 527 label_login_with_open_id_option: or login with OpenID
528 528 label_password_lost: Lost password
529 529 label_home: Home
530 530 label_my_page: My page
531 531 label_my_account: My account
532 532 label_my_projects: My projects
533 533 label_my_page_block: My page block
534 534 label_administration: Administration
535 535 label_login: Sign in
536 536 label_logout: Sign out
537 537 label_help: Help
538 538 label_reported_issues: Reported issues
539 539 label_assigned_to_me_issues: Issues assigned to me
540 540 label_last_login: Last connection
541 541 label_registered_on: Registered on
542 542 label_activity: Activity
543 543 label_overall_activity: Overall activity
544 544 label_user_activity: "%{value}'s activity"
545 545 label_new: New
546 546 label_logged_as: Logged in as
547 547 label_environment: Environment
548 548 label_authentication: Authentication
549 549 label_auth_source: Authentication mode
550 550 label_auth_source_new: New authentication mode
551 551 label_auth_source_plural: Authentication modes
552 552 label_subproject_plural: Subprojects
553 553 label_subproject_new: New subproject
554 554 label_and_its_subprojects: "%{value} and its subprojects"
555 555 label_min_max_length: Min - Max length
556 556 label_list: List
557 557 label_date: Date
558 558 label_integer: Integer
559 559 label_float: Float
560 560 label_boolean: Boolean
561 561 label_string: Text
562 562 label_text: Long text
563 563 label_attribute: Attribute
564 564 label_attribute_plural: Attributes
565 565 label_download: "%{count} Download"
566 566 label_download_plural: "%{count} Downloads"
567 567 label_no_data: No data to display
568 568 label_change_status: Change status
569 569 label_history: History
570 570 label_attachment: File
571 571 label_attachment_new: New file
572 572 label_attachment_delete: Delete file
573 573 label_attachment_plural: Files
574 574 label_file_added: File added
575 575 label_report: Report
576 576 label_report_plural: Reports
577 577 label_news: News
578 578 label_news_new: Add news
579 579 label_news_plural: News
580 580 label_news_latest: Latest news
581 581 label_news_view_all: View all news
582 582 label_news_added: News added
583 583 label_news_comment_added: Comment added to a news
584 584 label_settings: Settings
585 585 label_overview: Overview
586 586 label_version: Version
587 587 label_version_new: New version
588 588 label_version_plural: Versions
589 589 label_close_versions: Close completed versions
590 590 label_confirmation: Confirmation
591 591 label_export_to: 'Also available in:'
592 592 label_read: Read...
593 593 label_public_projects: Public projects
594 594 label_open_issues: open
595 595 label_open_issues_plural: open
596 596 label_closed_issues: closed
597 597 label_closed_issues_plural: closed
598 598 label_x_open_issues_abbr_on_total:
599 599 zero: 0 open / %{total}
600 600 one: 1 open / %{total}
601 601 other: "%{count} open / %{total}"
602 602 label_x_open_issues_abbr:
603 603 zero: 0 open
604 604 one: 1 open
605 605 other: "%{count} open"
606 606 label_x_closed_issues_abbr:
607 607 zero: 0 closed
608 608 one: 1 closed
609 609 other: "%{count} closed"
610 610 label_x_issues:
611 611 zero: 0 issues
612 612 one: 1 issue
613 613 other: "%{count} issues"
614 614 label_total: Total
615 615 label_permissions: Permissions
616 616 label_current_status: Current status
617 617 label_new_statuses_allowed: New statuses allowed
618 618 label_all: all
619 619 label_none: none
620 620 label_nobody: nobody
621 621 label_next: Next
622 622 label_previous: Previous
623 623 label_used_by: Used by
624 624 label_details: Details
625 625 label_add_note: Add a note
626 626 label_per_page: Per page
627 627 label_calendar: Calendar
628 628 label_months_from: months from
629 629 label_gantt: Gantt
630 630 label_internal: Internal
631 631 label_last_changes: "last %{count} changes"
632 632 label_change_view_all: View all changes
633 633 label_personalize_page: Personalize this page
634 634 label_comment: Comment
635 635 label_comment_plural: Comments
636 636 label_x_comments:
637 637 zero: no comments
638 638 one: 1 comment
639 639 other: "%{count} comments"
640 640 label_comment_add: Add a comment
641 641 label_comment_added: Comment added
642 642 label_comment_delete: Delete comments
643 643 label_query: Custom query
644 644 label_query_plural: Custom queries
645 645 label_query_new: New query
646 646 label_my_queries: My custom queries
647 647 label_filter_add: Add filter
648 648 label_filter_plural: Filters
649 649 label_equals: is
650 650 label_not_equals: is not
651 651 label_in_less_than: in less than
652 652 label_in_more_than: in more than
653 653 label_greater_or_equal: '>='
654 654 label_less_or_equal: '<='
655 655 label_between: between
656 656 label_in: in
657 657 label_today: today
658 658 label_all_time: all time
659 659 label_yesterday: yesterday
660 660 label_this_week: this week
661 661 label_last_week: last week
662 662 label_last_n_days: "last %{count} days"
663 663 label_this_month: this month
664 664 label_last_month: last month
665 665 label_this_year: this year
666 666 label_date_range: Date range
667 667 label_less_than_ago: less than days ago
668 668 label_more_than_ago: more than days ago
669 669 label_ago: days ago
670 670 label_contains: contains
671 671 label_not_contains: doesn't contain
672 672 label_day_plural: days
673 673 label_repository: Repository
674 674 label_repository_new: New repository
675 675 label_repository_plural: Repositories
676 676 label_browse: Browse
677 677 label_modification: "%{count} change"
678 678 label_modification_plural: "%{count} changes"
679 679 label_branch: Branch
680 680 label_tag: Tag
681 681 label_revision: Revision
682 682 label_revision_plural: Revisions
683 683 label_revision_id: "Revision %{value}"
684 684 label_associated_revisions: Associated revisions
685 685 label_added: added
686 686 label_modified: modified
687 687 label_copied: copied
688 688 label_renamed: renamed
689 689 label_deleted: deleted
690 690 label_latest_revision: Latest revision
691 691 label_latest_revision_plural: Latest revisions
692 692 label_view_revisions: View revisions
693 693 label_view_all_revisions: View all revisions
694 694 label_max_size: Maximum size
695 695 label_sort_highest: Move to top
696 696 label_sort_higher: Move up
697 697 label_sort_lower: Move down
698 698 label_sort_lowest: Move to bottom
699 699 label_roadmap: Roadmap
700 700 label_roadmap_due_in: "Due in %{value}"
701 701 label_roadmap_overdue: "%{value} late"
702 702 label_roadmap_no_issues: No issues for this version
703 703 label_search: Search
704 704 label_result_plural: Results
705 705 label_all_words: All words
706 706 label_wiki: Wiki
707 707 label_wiki_edit: Wiki edit
708 708 label_wiki_edit_plural: Wiki edits
709 709 label_wiki_page: Wiki page
710 710 label_wiki_page_plural: Wiki pages
711 711 label_index_by_title: Index by title
712 712 label_index_by_date: Index by date
713 713 label_current_version: Current version
714 714 label_preview: Preview
715 715 label_feed_plural: Feeds
716 716 label_changes_details: Details of all changes
717 717 label_issue_tracking: Issue tracking
718 718 label_spent_time: Spent time
719 719 label_overall_spent_time: Overall spent time
720 720 label_f_hour: "%{value} hour"
721 721 label_f_hour_plural: "%{value} hours"
722 722 label_time_tracking: Time tracking
723 723 label_change_plural: Changes
724 724 label_statistics: Statistics
725 725 label_commits_per_month: Commits per month
726 726 label_commits_per_author: Commits per author
727 727 label_diff: diff
728 728 label_view_diff: View differences
729 729 label_diff_inline: inline
730 730 label_diff_side_by_side: side by side
731 731 label_options: Options
732 732 label_copy_workflow_from: Copy workflow from
733 733 label_permissions_report: Permissions report
734 734 label_watched_issues: Watched issues
735 735 label_related_issues: Related issues
736 736 label_applied_status: Applied status
737 737 label_loading: Loading...
738 738 label_relation_new: New relation
739 739 label_relation_delete: Delete relation
740 740 label_relates_to: related to
741 741 label_duplicates: duplicates
742 742 label_duplicated_by: duplicated by
743 743 label_blocks: blocks
744 744 label_blocked_by: blocked by
745 745 label_precedes: precedes
746 746 label_follows: follows
747 747 label_end_to_start: end to start
748 748 label_end_to_end: end to end
749 749 label_start_to_start: start to start
750 750 label_start_to_end: start to end
751 751 label_stay_logged_in: Stay logged in
752 752 label_disabled: disabled
753 753 label_show_completed_versions: Show completed versions
754 754 label_me: me
755 755 label_board: Forum
756 756 label_board_new: New forum
757 757 label_board_plural: Forums
758 758 label_board_locked: Locked
759 759 label_board_sticky: Sticky
760 760 label_topic_plural: Topics
761 761 label_message_plural: Messages
762 762 label_message_last: Last message
763 763 label_message_new: New message
764 764 label_message_posted: Message added
765 765 label_reply_plural: Replies
766 766 label_send_information: Send account information to the user
767 767 label_year: Year
768 768 label_month: Month
769 769 label_week: Week
770 770 label_date_from: From
771 771 label_date_to: To
772 772 label_language_based: Based on user's language
773 773 label_sort_by: "Sort by %{value}"
774 774 label_send_test_email: Send a test email
775 775 label_feeds_access_key: RSS access key
776 776 label_missing_feeds_access_key: Missing a RSS access key
777 777 label_feeds_access_key_created_on: "RSS access key created %{value} ago"
778 778 label_module_plural: Modules
779 779 label_added_time_by: "Added by %{author} %{age} ago"
780 780 label_updated_time_by: "Updated by %{author} %{age} ago"
781 781 label_updated_time: "Updated %{value} ago"
782 782 label_jump_to_a_project: Jump to a project...
783 783 label_file_plural: Files
784 784 label_changeset_plural: Changesets
785 785 label_default_columns: Default columns
786 786 label_no_change_option: (No change)
787 787 label_bulk_edit_selected_issues: Bulk edit selected issues
788 788 label_bulk_edit_selected_time_entries: Bulk edit selected time entries
789 789 label_theme: Theme
790 790 label_default: Default
791 791 label_search_titles_only: Search titles only
792 792 label_user_mail_option_all: "For any event on all my projects"
793 793 label_user_mail_option_selected: "For any event on the selected projects only..."
794 794 label_user_mail_option_none: "No events"
795 795 label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in"
796 796 label_user_mail_option_only_assigned: "Only for things I am assigned to"
797 797 label_user_mail_option_only_owner: "Only for things I am the owner of"
798 798 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
799 799 label_registration_activation_by_email: account activation by email
800 800 label_registration_manual_activation: manual account activation
801 801 label_registration_automatic_activation: automatic account activation
802 802 label_display_per_page: "Per page: %{value}"
803 803 label_age: Age
804 804 label_change_properties: Change properties
805 805 label_general: General
806 806 label_more: More
807 807 label_scm: SCM
808 808 label_plugins: Plugins
809 809 label_ldap_authentication: LDAP authentication
810 810 label_downloads_abbr: D/L
811 811 label_optional_description: Optional description
812 812 label_add_another_file: Add another file
813 813 label_preferences: Preferences
814 814 label_chronological_order: In chronological order
815 815 label_reverse_chronological_order: In reverse chronological order
816 816 label_planning: Planning
817 817 label_incoming_emails: Incoming emails
818 818 label_generate_key: Generate a key
819 819 label_issue_watchers: Watchers
820 820 label_example: Example
821 821 label_display: Display
822 822 label_sort: Sort
823 823 label_ascending: Ascending
824 824 label_descending: Descending
825 825 label_date_from_to: From %{start} to %{end}
826 826 label_wiki_content_added: Wiki page added
827 827 label_wiki_content_updated: Wiki page updated
828 828 label_group: Group
829 829 label_group_plural: Groups
830 830 label_group_new: New group
831 831 label_time_entry_plural: Spent time
832 832 label_version_sharing_none: Not shared
833 833 label_version_sharing_descendants: With subprojects
834 834 label_version_sharing_hierarchy: With project hierarchy
835 835 label_version_sharing_tree: With project tree
836 836 label_version_sharing_system: With all projects
837 837 label_update_issue_done_ratios: Update issue done ratios
838 838 label_copy_source: Source
839 839 label_copy_target: Target
840 840 label_copy_same_as_target: Same as target
841 841 label_display_used_statuses_only: Only display statuses that are used by this tracker
842 842 label_api_access_key: API access key
843 843 label_missing_api_access_key: Missing an API access key
844 844 label_api_access_key_created_on: "API access key created %{value} ago"
845 845 label_profile: Profile
846 846 label_subtask_plural: Subtasks
847 847 label_project_copy_notifications: Send email notifications during the project copy
848 848 label_principal_search: "Search for user or group:"
849 849 label_user_search: "Search for user:"
850 850 label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
851 851 label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
852 852 label_issues_visibility_all: All issues
853 853 label_issues_visibility_public: All non private issues
854 854 label_issues_visibility_own: Issues created by or assigned to the user
855 855 label_git_report_last_commit: Report last commit for files and directories
856 856 label_parent_revision: Parent
857 857 label_child_revision: Child
858 858 label_export_options: "%{export_format} export options"
859 859 label_copy_attachments: Copy attachments
860 860 label_item_position: "%{position} of %{count}"
861 861 label_completed_versions: Completed versions
862 862 label_search_for_watchers: Search for watchers to add
863 863 label_session_expiration: Session expiration
864 864 label_show_closed_projects: View closed projects
865 865 label_status_transitions: Status transitions
866 866 label_fields_permissions: Fields permissions
867 867 label_readonly: Read-only
868 868 label_required: Required
869 label_attribute_of_project: "Project's %{name}"
870 label_attribute_of_author: "Author's %{name}"
871 label_attribute_of_assigned_to: "Assignee's %{name}"
872 label_attribute_of_fixed_version: "Target version's %{name}"
869 873
870 874 button_login: Login
871 875 button_submit: Submit
872 876 button_save: Save
873 877 button_check_all: Check all
874 878 button_uncheck_all: Uncheck all
875 879 button_collapse_all: Collapse all
876 880 button_expand_all: Expand all
877 881 button_delete: Delete
878 882 button_create: Create
879 883 button_create_and_continue: Create and continue
880 884 button_test: Test
881 885 button_edit: Edit
882 886 button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
883 887 button_add: Add
884 888 button_change: Change
885 889 button_apply: Apply
886 890 button_clear: Clear
887 891 button_lock: Lock
888 892 button_unlock: Unlock
889 893 button_download: Download
890 894 button_list: List
891 895 button_view: View
892 896 button_move: Move
893 897 button_move_and_follow: Move and follow
894 898 button_back: Back
895 899 button_cancel: Cancel
896 900 button_activate: Activate
897 901 button_sort: Sort
898 902 button_log_time: Log time
899 903 button_rollback: Rollback to this version
900 904 button_watch: Watch
901 905 button_unwatch: Unwatch
902 906 button_reply: Reply
903 907 button_archive: Archive
904 908 button_unarchive: Unarchive
905 909 button_reset: Reset
906 910 button_rename: Rename
907 911 button_change_password: Change password
908 912 button_copy: Copy
909 913 button_copy_and_follow: Copy and follow
910 914 button_annotate: Annotate
911 915 button_update: Update
912 916 button_configure: Configure
913 917 button_quote: Quote
914 918 button_duplicate: Duplicate
915 919 button_show: Show
916 920 button_edit_section: Edit this section
917 921 button_export: Export
918 922 button_delete_my_account: Delete my account
919 923 button_close: Close
920 924 button_reopen: Reopen
921 925
922 926 status_active: active
923 927 status_registered: registered
924 928 status_locked: locked
925 929
926 930 project_status_active: active
927 931 project_status_closed: closed
928 932 project_status_archived: archived
929 933
930 934 version_status_open: open
931 935 version_status_locked: locked
932 936 version_status_closed: closed
933 937
934 938 field_active: Active
935 939
936 940 text_select_mail_notifications: Select actions for which email notifications should be sent.
937 941 text_regexp_info: eg. ^[A-Z0-9]+$
938 942 text_min_max_length_info: 0 means no restriction
939 943 text_project_destroy_confirmation: Are you sure you want to delete this project and related data?
940 944 text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
941 945 text_workflow_edit: Select a role and a tracker to edit the workflow
942 946 text_are_you_sure: Are you sure?
943 947 text_are_you_sure_with_children: "Delete issue and all child issues?"
944 948 text_journal_changed: "%{label} changed from %{old} to %{new}"
945 949 text_journal_changed_no_detail: "%{label} updated"
946 950 text_journal_set_to: "%{label} set to %{value}"
947 951 text_journal_deleted: "%{label} deleted (%{old})"
948 952 text_journal_added: "%{label} %{value} added"
949 953 text_tip_issue_begin_day: issue beginning this day
950 954 text_tip_issue_end_day: issue ending this day
951 955 text_tip_issue_begin_end_day: issue beginning and ending this day
952 956 text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
953 957 text_caracters_maximum: "%{count} characters maximum."
954 958 text_caracters_minimum: "Must be at least %{count} characters long."
955 959 text_length_between: "Length between %{min} and %{max} characters."
956 960 text_tracker_no_workflow: No workflow defined for this tracker
957 961 text_unallowed_characters: Unallowed characters
958 962 text_comma_separated: Multiple values allowed (comma separated).
959 963 text_line_separated: Multiple values allowed (one line for each value).
960 964 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
961 965 text_issue_added: "Issue %{id} has been reported by %{author}."
962 966 text_issue_updated: "Issue %{id} has been updated by %{author}."
963 967 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content?
964 968 text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?"
965 969 text_issue_category_destroy_assignments: Remove category assignments
966 970 text_issue_category_reassign_to: Reassign issues to this category
967 971 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)."
968 972 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."
969 973 text_load_default_configuration: Load the default configuration
970 974 text_status_changed_by_changeset: "Applied in changeset %{value}."
971 975 text_time_logged_by_changeset: "Applied in changeset %{value}."
972 976 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
973 977 text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)."
974 978 text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?'
975 979 text_select_project_modules: 'Select modules to enable for this project:'
976 980 text_default_administrator_account_changed: Default administrator account changed
977 981 text_file_repository_writable: Attachments directory writable
978 982 text_plugin_assets_writable: Plugin assets directory writable
979 983 text_rmagick_available: RMagick available (optional)
980 984 text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?"
981 985 text_destroy_time_entries: Delete reported hours
982 986 text_assign_time_entries_to_project: Assign reported hours to the project
983 987 text_reassign_time_entries: 'Reassign reported hours to this issue:'
984 988 text_user_wrote: "%{value} wrote:"
985 989 text_enumeration_destroy_question: "%{count} objects are assigned to this value."
986 990 text_enumeration_category_reassign_to: 'Reassign them to this value:'
987 991 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."
988 992 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."
989 993 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
990 994 text_custom_field_possible_values_info: 'One line for each value'
991 995 text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?"
992 996 text_wiki_page_nullify_children: "Keep child pages as root pages"
993 997 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
994 998 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
995 999 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?"
996 1000 text_zoom_in: Zoom in
997 1001 text_zoom_out: Zoom out
998 1002 text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page."
999 1003 text_scm_path_encoding_note: "Default: UTF-8"
1000 1004 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
1001 1005 text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo)
1002 1006 text_scm_command: Command
1003 1007 text_scm_command_version: Version
1004 1008 text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it.
1005 1009 text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
1006 1010 text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)"
1007 1011 text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes"
1008 1012 text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}"
1009 1013 text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it."
1010 1014 text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours."
1011 1015 text_project_closed: This project is closed and read-only.
1012 1016
1013 1017 default_role_manager: Manager
1014 1018 default_role_developer: Developer
1015 1019 default_role_reporter: Reporter
1016 1020 default_tracker_bug: Bug
1017 1021 default_tracker_feature: Feature
1018 1022 default_tracker_support: Support
1019 1023 default_issue_status_new: New
1020 1024 default_issue_status_in_progress: In Progress
1021 1025 default_issue_status_resolved: Resolved
1022 1026 default_issue_status_feedback: Feedback
1023 1027 default_issue_status_closed: Closed
1024 1028 default_issue_status_rejected: Rejected
1025 1029 default_doc_category_user: User documentation
1026 1030 default_doc_category_tech: Technical documentation
1027 1031 default_priority_low: Low
1028 1032 default_priority_normal: Normal
1029 1033 default_priority_high: High
1030 1034 default_priority_urgent: Urgent
1031 1035 default_priority_immediate: Immediate
1032 1036 default_activity_design: Design
1033 1037 default_activity_development: Development
1034 1038
1035 1039 enumeration_issue_priorities: Issue priorities
1036 1040 enumeration_doc_categories: Document categories
1037 1041 enumeration_activities: Activities (time tracking)
1038 1042 enumeration_system_activity: System Activity
1039 1043 description_filter: Filter
1040 1044 description_search: Searchfield
1041 1045 description_choose_project: Projects
1042 1046 description_project_scope: Search scope
1043 1047 description_notes: Notes
1044 1048 description_message_content: Message content
1045 1049 description_query_sort_criteria_attribute: Sort attribute
1046 1050 description_query_sort_criteria_direction: Sort direction
1047 1051 description_user_mail_notification: Mail notification settings
1048 1052 description_available_columns: Available Columns
1049 1053 description_selected_columns: Selected Columns
1050 1054 description_all_columns: All Columns
1051 1055 description_issue_category_reassign: Choose issue category
1052 1056 description_wiki_subpages_reassign: Choose new parent page
1053 1057 description_date_range_list: Choose range from list
1054 1058 description_date_range_interval: Choose range by selecting start and end date
1055 1059 description_date_from: Enter start date
1056 1060 description_date_to: Enter end date
1057 1061 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,1074 +1,1078
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 month_names: [~, janvier, fΓ©vrier, mars, avril, mai, juin, juillet, aoΓ»t, septembre, octobre, novembre, dΓ©cembre]
19 19 abbr_month_names: [~, jan., fΓ©v., mar., avr., mai, juin, juil., aoΓ»t, sept., oct., nov., dΓ©c.]
20 20 order:
21 21 - :day
22 22 - :month
23 23 - :year
24 24
25 25 time:
26 26 formats:
27 27 default: "%d/%m/%Y %H:%M"
28 28 time: "%H:%M"
29 29 short: "%d %b %H:%M"
30 30 long: "%A %d %B %Y %H:%M:%S %Z"
31 31 long_ordinal: "%A %d %B %Y %H:%M:%S %Z"
32 32 only_second: "%S"
33 33 am: 'am'
34 34 pm: 'pm'
35 35
36 36 datetime:
37 37 distance_in_words:
38 38 half_a_minute: "30 secondes"
39 39 less_than_x_seconds:
40 40 zero: "moins d'une seconde"
41 41 one: "moins d'uneΒ seconde"
42 42 other: "moins de %{count}Β secondes"
43 43 x_seconds:
44 44 one: "1Β seconde"
45 45 other: "%{count}Β secondes"
46 46 less_than_x_minutes:
47 47 zero: "moins d'une minute"
48 48 one: "moins d'uneΒ minute"
49 49 other: "moins de %{count}Β minutes"
50 50 x_minutes:
51 51 one: "1Β minute"
52 52 other: "%{count}Β minutes"
53 53 about_x_hours:
54 54 one: "environ une heure"
55 55 other: "environ %{count}Β heures"
56 56 x_hours:
57 57 one: "une heure"
58 58 other: "%{count}Β heures"
59 59 x_days:
60 60 one: "unΒ jour"
61 61 other: "%{count}Β jours"
62 62 about_x_months:
63 63 one: "environ un mois"
64 64 other: "environ %{count}Β mois"
65 65 x_months:
66 66 one: "unΒ mois"
67 67 other: "%{count}Β mois"
68 68 about_x_years:
69 69 one: "environ un an"
70 70 other: "environ %{count}Β ans"
71 71 over_x_years:
72 72 one: "plus d'un an"
73 73 other: "plus de %{count}Β ans"
74 74 almost_x_years:
75 75 one: "presqu'un an"
76 76 other: "presque %{count} ans"
77 77 prompts:
78 78 year: "AnnΓ©e"
79 79 month: "Mois"
80 80 day: "Jour"
81 81 hour: "Heure"
82 82 minute: "Minute"
83 83 second: "Seconde"
84 84
85 85 number:
86 86 format:
87 87 precision: 3
88 88 separator: ','
89 89 delimiter: 'Β '
90 90 currency:
91 91 format:
92 92 unit: '€'
93 93 precision: 2
94 94 format: '%nΒ %u'
95 95 human:
96 96 format:
97 97 precision: 3
98 98 storage_units:
99 99 format: "%n %u"
100 100 units:
101 101 byte:
102 102 one: "octet"
103 103 other: "octet"
104 104 kb: "ko"
105 105 mb: "Mo"
106 106 gb: "Go"
107 107 tb: "To"
108 108
109 109 support:
110 110 array:
111 111 sentence_connector: 'et'
112 112 skip_last_comma: true
113 113 word_connector: ", "
114 114 two_words_connector: " et "
115 115 last_word_connector: " et "
116 116
117 117 activerecord:
118 118 errors:
119 119 template:
120 120 header:
121 121 one: "Impossible d'enregistrer %{model} : une erreur"
122 122 other: "Impossible d'enregistrer %{model} : %{count} erreurs."
123 123 body: "Veuillez vΓ©rifier les champs suivantsΒ :"
124 124 messages:
125 125 inclusion: "n'est pas inclus(e) dans la liste"
126 126 exclusion: "n'est pas disponible"
127 127 invalid: "n'est pas valide"
128 128 confirmation: "ne concorde pas avec la confirmation"
129 129 accepted: "doit Γͺtre acceptΓ©(e)"
130 130 empty: "doit Γͺtre renseignΓ©(e)"
131 131 blank: "doit Γͺtre renseignΓ©(e)"
132 132 too_long: "est trop long (pas plus de %{count} caractères)"
133 133 too_short: "est trop court (au moins %{count} caractères)"
134 134 wrong_length: "ne fait pas la bonne longueur (doit comporter %{count} caractères)"
135 135 taken: "est dΓ©jΓ  utilisΓ©"
136 136 not_a_number: "n'est pas un nombre"
137 137 not_a_date: "n'est pas une date valide"
138 138 greater_than: "doit Γͺtre supΓ©rieur Γ  %{count}"
139 139 greater_than_or_equal_to: "doit Γͺtre supΓ©rieur ou Γ©gal Γ  %{count}"
140 140 equal_to: "doit Γͺtre Γ©gal Γ  %{count}"
141 141 less_than: "doit Γͺtre infΓ©rieur Γ  %{count}"
142 142 less_than_or_equal_to: "doit Γͺtre infΓ©rieur ou Γ©gal Γ  %{count}"
143 143 odd: "doit Γͺtre impair"
144 144 even: "doit Γͺtre pair"
145 145 greater_than_start_date: "doit Γͺtre postΓ©rieure Γ  la date de dΓ©but"
146 146 not_same_project: "n'appartient pas au mΓͺme projet"
147 147 circular_dependency: "Cette relation crΓ©erait une dΓ©pendance circulaire"
148 148 cant_link_an_issue_with_a_descendant: "Une demande ne peut pas Γͺtre liΓ©e Γ  l'une de ses sous-tΓ’ches"
149 149
150 150 actionview_instancetag_blank_option: Choisir
151 151
152 152 general_text_No: 'Non'
153 153 general_text_Yes: 'Oui'
154 154 general_text_no: 'non'
155 155 general_text_yes: 'oui'
156 156 general_lang_name: 'FranΓ§ais'
157 157 general_csv_separator: ';'
158 158 general_csv_decimal_separator: ','
159 159 general_csv_encoding: ISO-8859-1
160 160 general_pdf_encoding: UTF-8
161 161 general_first_day_of_week: '1'
162 162
163 163 notice_account_updated: Le compte a été mis à jour avec succès.
164 164 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
165 165 notice_account_password_updated: Mot de passe mis à jour avec succès.
166 166 notice_account_wrong_password: Mot de passe incorrect
167 167 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a Γ©tΓ© envoyΓ©.
168 168 notice_account_unknown_email: Aucun compte ne correspond Γ  cette adresse.
169 169 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
170 170 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a Γ©tΓ© envoyΓ©.
171 171 notice_account_activated: Votre compte a Γ©tΓ© activΓ©. Vous pouvez Γ  prΓ©sent vous connecter.
172 172 notice_successful_create: Création effectuée avec succès.
173 173 notice_successful_update: Mise à jour effectuée avec succès.
174 174 notice_successful_delete: Suppression effectuée avec succès.
175 175 notice_successful_connection: Connexion rΓ©ussie.
176 176 notice_file_not_found: "La page Γ  laquelle vous souhaitez accΓ©der n'existe pas ou a Γ©tΓ© supprimΓ©e."
177 177 notice_locking_conflict: Les donnΓ©es ont Γ©tΓ© mises Γ  jour par un autre utilisateur. Mise Γ  jour impossible.
178 178 notice_not_authorized: "Vous n'Γͺtes pas autorisΓ© Γ  accΓ©der Γ  cette page."
179 179 notice_not_authorized_archived_project: Le projet auquel vous tentez d'accΓ©der a Γ©tΓ© archivΓ©.
180 180 notice_email_sent: "Un email a Γ©tΓ© envoyΓ© Γ  %{value}"
181 181 notice_email_error: "Erreur lors de l'envoi de l'email (%{value})"
182 182 notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée."
183 183 notice_failed_to_save_issues: "%{count} demande(s) sur les %{total} sΓ©lectionnΓ©es n'ont pas pu Γͺtre mise(s) Γ  jour : %{ids}."
184 184 notice_failed_to_save_time_entries: "%{count} temps passΓ©(s) sur les %{total} sΓ©lectionnΓ©s n'ont pas pu Γͺtre mis Γ  jour: %{ids}."
185 185 notice_no_issue_selected: "Aucune demande sΓ©lectionnΓ©e ! Cochez les demandes que vous voulez mettre Γ  jour."
186 186 notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur."
187 187 notice_default_data_loaded: Paramétrage par défaut chargé avec succès.
188 188 notice_unable_delete_version: Impossible de supprimer cette version.
189 189 notice_issue_done_ratios_updated: L'avancement des demandes a Γ©tΓ© mis Γ  jour.
190 190 notice_api_access_key_reseted: Votre clé d'accès API a été réinitialisée.
191 191 notice_gantt_chart_truncated: "Le diagramme a Γ©tΓ© tronquΓ© car il excΓ¨de le nombre maximal d'Γ©lΓ©ments pouvant Γͺtre affichΓ©s (%{max})"
192 192 notice_issue_successful_create: "Demande %{id} créée."
193 193 notice_issue_update_conflict: "La demande a Γ©tΓ© mise Γ  jour par un autre utilisateur pendant que vous la modifiez."
194 194 notice_account_deleted: "Votre compte a Γ©tΓ© dΓ©finitivement supprimΓ©."
195 195 notice_user_successful_create: "Utilisateur %{id} créé."
196 196
197 197 error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramΓ©trage : %{value}"
198 198 error_scm_not_found: "L'entrΓ©e et/ou la rΓ©vision demandΓ©e n'existe pas dans le dΓ©pΓ΄t."
199 199 error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt : %{value}"
200 200 error_scm_annotate: "L'entrΓ©e n'existe pas ou ne peut pas Γͺtre annotΓ©e."
201 201 error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ  ce projet"
202 202 error_can_not_reopen_issue_on_closed_version: 'Une demande assignΓ©e Γ  une version fermΓ©e ne peut pas Γͺtre rΓ©ouverte'
203 203 error_can_not_archive_project: "Ce projet ne peut pas Γͺtre archivΓ©"
204 204 error_workflow_copy_source: 'Veuillez sΓ©lectionner un tracker et/ou un rΓ΄le source'
205 205 error_workflow_copy_target: 'Veuillez sΓ©lectionner les trackers et rΓ΄les cibles'
206 206 error_issue_done_ratios_not_updated: L'avancement des demandes n'a pas pu Γͺtre mis Γ  jour.
207 207 error_attachment_too_big: Ce fichier ne peut pas Γͺtre attachΓ© car il excΓ¨de la taille maximale autorisΓ©e (%{max_size})
208 208 error_session_expired: "Votre session a expirΓ©. Veuillez vous reconnecter."
209 209
210 210 warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu Γͺtre sauvegardΓ©s."
211 211
212 212 mail_subject_lost_password: "Votre mot de passe %{value}"
213 213 mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant :'
214 214 mail_subject_register: "Activation de votre compte %{value}"
215 215 mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant :'
216 216 mail_body_account_information_external: "Vous pouvez utiliser votre compte %{value} pour vous connecter."
217 217 mail_body_account_information: Paramètres de connexion de votre compte
218 218 mail_subject_account_activation_request: "Demande d'activation d'un compte %{value}"
219 219 mail_body_account_activation_request: "Un nouvel utilisateur (%{value}) s'est inscrit. Son compte nΓ©cessite votre approbation :"
220 220 mail_subject_reminder: "%{count} demande(s) arrivent Γ  Γ©chΓ©ance (%{days})"
221 221 mail_body_reminder: "%{count} demande(s) qui vous sont assignΓ©es arrivent Γ  Γ©chΓ©ance dans les %{days} prochains jours :"
222 222 mail_subject_wiki_content_added: "Page wiki '%{id}' ajoutΓ©e"
223 223 mail_body_wiki_content_added: "La page wiki '%{id}' a Γ©tΓ© ajoutΓ©e par %{author}."
224 224 mail_subject_wiki_content_updated: "Page wiki '%{id}' mise Γ  jour"
225 225 mail_body_wiki_content_updated: "La page wiki '%{id}' a Γ©tΓ© mise Γ  jour par %{author}."
226 226
227 227 gui_validation_error: 1 erreur
228 228 gui_validation_error_plural: "%{count} erreurs"
229 229
230 230 field_name: Nom
231 231 field_description: Description
232 232 field_summary: RΓ©sumΓ©
233 233 field_is_required: Obligatoire
234 234 field_firstname: PrΓ©nom
235 235 field_lastname: Nom
236 236 field_mail: "Email "
237 237 field_filename: Fichier
238 238 field_filesize: Taille
239 239 field_downloads: TΓ©lΓ©chargements
240 240 field_author: Auteur
241 241 field_created_on: "Créé "
242 242 field_updated_on: "Mis-Γ -jour "
243 243 field_field_format: Format
244 244 field_is_for_all: Pour tous les projets
245 245 field_possible_values: Valeurs possibles
246 246 field_regexp: Expression régulière
247 247 field_min_length: Longueur minimum
248 248 field_max_length: Longueur maximum
249 249 field_value: Valeur
250 250 field_category: CatΓ©gorie
251 251 field_title: Titre
252 252 field_project: Projet
253 253 field_issue: Demande
254 254 field_status: Statut
255 255 field_notes: Notes
256 256 field_is_closed: Demande fermΓ©e
257 257 field_is_default: Valeur par dΓ©faut
258 258 field_tracker: Tracker
259 259 field_subject: Sujet
260 260 field_due_date: EchΓ©ance
261 261 field_assigned_to: AssignΓ© Γ 
262 262 field_priority: PrioritΓ©
263 263 field_fixed_version: Version cible
264 264 field_user: Utilisateur
265 265 field_role: RΓ΄le
266 266 field_homepage: "Site web "
267 267 field_is_public: Public
268 268 field_parent: Sous-projet de
269 269 field_is_in_roadmap: Demandes affichΓ©es dans la roadmap
270 270 field_login: "Identifiant "
271 271 field_mail_notification: Notifications par mail
272 272 field_admin: Administrateur
273 273 field_last_login_on: "Dernière connexion "
274 274 field_language: Langue
275 275 field_effective_date: Date
276 276 field_password: Mot de passe
277 277 field_new_password: Nouveau mot de passe
278 278 field_password_confirmation: Confirmation
279 279 field_version: Version
280 280 field_type: Type
281 281 field_host: HΓ΄te
282 282 field_port: Port
283 283 field_account: Compte
284 284 field_base_dn: Base DN
285 285 field_attr_login: Attribut Identifiant
286 286 field_attr_firstname: Attribut PrΓ©nom
287 287 field_attr_lastname: Attribut Nom
288 288 field_attr_mail: Attribut Email
289 289 field_onthefly: CrΓ©ation des utilisateurs Γ  la volΓ©e
290 290 field_start_date: DΓ©but
291 291 field_done_ratio: "% rΓ©alisΓ©"
292 292 field_auth_source: Mode d'authentification
293 293 field_hide_mail: Cacher mon adresse mail
294 294 field_comments: Commentaire
295 295 field_url: URL
296 296 field_start_page: Page de dΓ©marrage
297 297 field_subproject: Sous-projet
298 298 field_hours: Heures
299 299 field_activity: ActivitΓ©
300 300 field_spent_on: Date
301 301 field_identifier: Identifiant
302 302 field_is_filter: UtilisΓ© comme filtre
303 303 field_issue_to: Demande liΓ©e
304 304 field_delay: Retard
305 305 field_assignable: Demandes assignables Γ  ce rΓ΄le
306 306 field_redirect_existing_links: Rediriger les liens existants
307 307 field_estimated_hours: Temps estimΓ©
308 308 field_column_names: Colonnes
309 309 field_time_zone: Fuseau horaire
310 310 field_searchable: UtilisΓ© pour les recherches
311 311 field_default_value: Valeur par dΓ©faut
312 312 field_comments_sorting: Afficher les commentaires
313 313 field_parent_title: Page parent
314 314 field_editable: Modifiable
315 315 field_watcher: Observateur
316 316 field_identity_url: URL OpenID
317 317 field_content: Contenu
318 318 field_group_by: Grouper par
319 319 field_sharing: Partage
320 320 field_active: Actif
321 321 field_parent_issue: TΓ’che parente
322 322 field_visible: Visible
323 323 field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardΓ©"
324 324 field_issues_visibility: VisibilitΓ© des demandes
325 325 field_is_private: PrivΓ©e
326 326 field_commit_logs_encoding: Encodage des messages de commit
327 327 field_repository_is_default: DΓ©pΓ΄t principal
328 328 field_multiple: Valeurs multiples
329 329 field_auth_source_ldap_filter: Filtre LDAP
330 330 field_core_fields: Champs standards
331 331 field_timeout: "Timeout (en secondes)"
332 332 field_board_parent: Forum parent
333 333
334 334 setting_app_title: Titre de l'application
335 335 setting_app_subtitle: Sous-titre de l'application
336 336 setting_welcome_text: Texte d'accueil
337 337 setting_default_language: Langue par dΓ©faut
338 338 setting_login_required: Authentification obligatoire
339 339 setting_self_registration: Inscription des nouveaux utilisateurs
340 340 setting_attachment_max_size: Taille maximale des fichiers
341 341 setting_issues_export_limit: Limite d'exportation des demandes
342 342 setting_mail_from: Adresse d'Γ©mission
343 343 setting_bcc_recipients: Destinataires en copie cachΓ©e (cci)
344 344 setting_plain_text_mail: Mail en texte brut (non HTML)
345 345 setting_host_name: Nom d'hΓ΄te et chemin
346 346 setting_text_formatting: Formatage du texte
347 347 setting_wiki_compression: Compression de l'historique des pages wiki
348 348 setting_feeds_limit: Nombre maximal d'Γ©lΓ©ments dans les flux Atom
349 349 setting_default_projects_public: DΓ©finir les nouveaux projets comme publics par dΓ©faut
350 350 setting_autofetch_changesets: RΓ©cupΓ©ration automatique des commits
351 351 setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts
352 352 setting_commit_ref_keywords: Mots-clΓ©s de rΓ©fΓ©rencement
353 353 setting_commit_fix_keywords: Mots-clΓ©s de rΓ©solution
354 354 setting_autologin: DurΓ©e maximale de connexion automatique
355 355 setting_date_format: Format de date
356 356 setting_time_format: Format d'heure
357 357 setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets
358 358 setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes
359 359 setting_emails_footer: Pied-de-page des emails
360 360 setting_protocol: Protocole
361 361 setting_per_page_options: Options d'objets affichΓ©s par page
362 362 setting_user_format: Format d'affichage des utilisateurs
363 363 setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets
364 364 setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux
365 365 setting_enabled_scm: SCM activΓ©s
366 366 setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes"
367 367 setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails"
368 368 setting_mail_handler_api_key: ClΓ© de protection de l'API
369 369 setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels
370 370 setting_gravatar_enabled: Afficher les Gravatar des utilisateurs
371 371 setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es
372 372 setting_file_max_size_displayed: Taille maximum des fichiers texte affichΓ©s en ligne
373 373 setting_repository_log_display_limit: "Nombre maximum de rΓ©visions affichΓ©es sur l'historique d'un fichier"
374 374 setting_openid: "Autoriser l'authentification et l'enregistrement OpenID"
375 375 setting_password_min_length: Longueur minimum des mots de passe
376 376 setting_new_project_user_role_id: RΓ΄le donnΓ© Γ  un utilisateur non-administrateur qui crΓ©e un projet
377 377 setting_default_projects_modules: Modules activΓ©s par dΓ©faut pour les nouveaux projets
378 378 setting_issue_done_ratio: Calcul de l'avancement des demandes
379 379 setting_issue_done_ratio_issue_status: Utiliser le statut
380 380 setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectuΓ©'
381 381 setting_rest_api_enabled: Activer l'API REST
382 382 setting_gravatar_default: Image Gravatar par dΓ©faut
383 383 setting_start_of_week: Jour de dΓ©but des calendriers
384 384 setting_cache_formatted_text: Mettre en cache le texte formatΓ©
385 385 setting_commit_logtime_enabled: Permettre la saisie de temps
386 386 setting_commit_logtime_activity_id: ActivitΓ© pour le temps saisi
387 387 setting_gantt_items_limit: Nombre maximum d'Γ©lΓ©ments affichΓ©s sur le gantt
388 388 setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes
389 389 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
390 390 setting_commit_cross_project_ref: Permettre le rΓ©fΓ©rencement et la rΓ©solution des demandes de tous les autres projets
391 391 setting_unsubscribe: Permettre aux utilisateurs de supprimer leur propre compte
392 392 setting_session_lifetime: DurΓ©e de vie maximale des sessions
393 393 setting_session_timeout: DurΓ©e maximale d'inactivitΓ©
394 394 setting_thumbnails_enabled: Afficher les vignettes des images
395 395 setting_thumbnails_size: Taille des vignettes (en pixels)
396 396
397 397 permission_add_project: CrΓ©er un projet
398 398 permission_add_subprojects: CrΓ©er des sous-projets
399 399 permission_edit_project: Modifier le projet
400 400 permission_close_project: Fermer / rΓ©ouvrir le projet
401 401 permission_select_project_modules: Choisir les modules
402 402 permission_manage_members: GΓ©rer les membres
403 403 permission_manage_versions: GΓ©rer les versions
404 404 permission_manage_categories: GΓ©rer les catΓ©gories de demandes
405 405 permission_view_issues: Voir les demandes
406 406 permission_add_issues: CrΓ©er des demandes
407 407 permission_edit_issues: Modifier les demandes
408 408 permission_manage_issue_relations: GΓ©rer les relations
409 409 permission_set_issues_private: Rendre les demandes publiques ou privΓ©es
410 410 permission_set_own_issues_private: Rendre ses propres demandes publiques ou privΓ©es
411 411 permission_add_issue_notes: Ajouter des notes
412 412 permission_edit_issue_notes: Modifier les notes
413 413 permission_edit_own_issue_notes: Modifier ses propres notes
414 414 permission_move_issues: DΓ©placer les demandes
415 415 permission_delete_issues: Supprimer les demandes
416 416 permission_manage_public_queries: GΓ©rer les requΓͺtes publiques
417 417 permission_save_queries: Sauvegarder les requΓͺtes
418 418 permission_view_gantt: Voir le gantt
419 419 permission_view_calendar: Voir le calendrier
420 420 permission_view_issue_watchers: Voir la liste des observateurs
421 421 permission_add_issue_watchers: Ajouter des observateurs
422 422 permission_delete_issue_watchers: Supprimer des observateurs
423 423 permission_log_time: Saisir le temps passΓ©
424 424 permission_view_time_entries: Voir le temps passΓ©
425 425 permission_edit_time_entries: Modifier les temps passΓ©s
426 426 permission_edit_own_time_entries: Modifier son propre temps passΓ©
427 427 permission_manage_news: GΓ©rer les annonces
428 428 permission_comment_news: Commenter les annonces
429 429 permission_manage_documents: GΓ©rer les documents
430 430 permission_view_documents: Voir les documents
431 431 permission_manage_files: GΓ©rer les fichiers
432 432 permission_view_files: Voir les fichiers
433 433 permission_manage_wiki: GΓ©rer le wiki
434 434 permission_rename_wiki_pages: Renommer les pages
435 435 permission_delete_wiki_pages: Supprimer les pages
436 436 permission_view_wiki_pages: Voir le wiki
437 437 permission_view_wiki_edits: "Voir l'historique des modifications"
438 438 permission_edit_wiki_pages: Modifier les pages
439 439 permission_delete_wiki_pages_attachments: Supprimer les fichiers joints
440 440 permission_protect_wiki_pages: ProtΓ©ger les pages
441 441 permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources
442 442 permission_browse_repository: Parcourir les sources
443 443 permission_view_changesets: Voir les rΓ©visions
444 444 permission_commit_access: Droit de commit
445 445 permission_manage_boards: GΓ©rer les forums
446 446 permission_view_messages: Voir les messages
447 447 permission_add_messages: Poster un message
448 448 permission_edit_messages: Modifier les messages
449 449 permission_edit_own_messages: Modifier ses propres messages
450 450 permission_delete_messages: Supprimer les messages
451 451 permission_delete_own_messages: Supprimer ses propres messages
452 452 permission_export_wiki_pages: Exporter les pages
453 453 permission_manage_project_activities: GΓ©rer les activitΓ©s
454 454 permission_manage_subtasks: GΓ©rer les sous-tΓ’ches
455 455 permission_manage_related_issues: GΓ©rer les demandes associΓ©es
456 456
457 457 project_module_issue_tracking: Suivi des demandes
458 458 project_module_time_tracking: Suivi du temps passΓ©
459 459 project_module_news: Publication d'annonces
460 460 project_module_documents: Publication de documents
461 461 project_module_files: Publication de fichiers
462 462 project_module_wiki: Wiki
463 463 project_module_repository: DΓ©pΓ΄t de sources
464 464 project_module_boards: Forums de discussion
465 465
466 466 label_user: Utilisateur
467 467 label_user_plural: Utilisateurs
468 468 label_user_new: Nouvel utilisateur
469 469 label_user_anonymous: Anonyme
470 470 label_project: Projet
471 471 label_project_new: Nouveau projet
472 472 label_project_plural: Projets
473 473 label_x_projects:
474 474 zero: aucun projet
475 475 one: un projet
476 476 other: "%{count} projets"
477 477 label_project_all: Tous les projets
478 478 label_project_latest: Derniers projets
479 479 label_issue: Demande
480 480 label_issue_new: Nouvelle demande
481 481 label_issue_plural: Demandes
482 482 label_issue_view_all: Voir toutes les demandes
483 483 label_issue_added: Demande ajoutΓ©e
484 484 label_issue_updated: Demande mise Γ  jour
485 485 label_issue_note_added: Note ajoutΓ©e
486 486 label_issue_status_updated: Statut changΓ©
487 487 label_issue_priority_updated: PrioritΓ© changΓ©e
488 488 label_issues_by: "Demandes par %{value}"
489 489 label_document: Document
490 490 label_document_new: Nouveau document
491 491 label_document_plural: Documents
492 492 label_document_added: Document ajoutΓ©
493 493 label_role: RΓ΄le
494 494 label_role_plural: RΓ΄les
495 495 label_role_new: Nouveau rΓ΄le
496 496 label_role_and_permissions: RΓ΄les et permissions
497 497 label_role_anonymous: Anonyme
498 498 label_role_non_member: Non membre
499 499 label_member: Membre
500 500 label_member_new: Nouveau membre
501 501 label_member_plural: Membres
502 502 label_tracker: Tracker
503 503 label_tracker_plural: Trackers
504 504 label_tracker_new: Nouveau tracker
505 505 label_workflow: Workflow
506 506 label_issue_status: Statut de demandes
507 507 label_issue_status_plural: Statuts de demandes
508 508 label_issue_status_new: Nouveau statut
509 509 label_issue_category: CatΓ©gorie de demandes
510 510 label_issue_category_plural: CatΓ©gories de demandes
511 511 label_issue_category_new: Nouvelle catΓ©gorie
512 512 label_custom_field: Champ personnalisΓ©
513 513 label_custom_field_plural: Champs personnalisΓ©s
514 514 label_custom_field_new: Nouveau champ personnalisΓ©
515 515 label_enumerations: Listes de valeurs
516 516 label_enumeration_new: Nouvelle valeur
517 517 label_information: Information
518 518 label_information_plural: Informations
519 519 label_please_login: Identification
520 520 label_register: S'enregistrer
521 521 label_login_with_open_id_option: S'authentifier avec OpenID
522 522 label_password_lost: Mot de passe perdu
523 523 label_home: Accueil
524 524 label_my_page: Ma page
525 525 label_my_account: Mon compte
526 526 label_my_projects: Mes projets
527 527 label_my_page_block: Blocs disponibles
528 528 label_administration: Administration
529 529 label_login: Connexion
530 530 label_logout: DΓ©connexion
531 531 label_help: Aide
532 532 label_reported_issues: "Demandes soumises "
533 533 label_assigned_to_me_issues: Demandes qui me sont assignΓ©es
534 534 label_last_login: "Dernière connexion "
535 535 label_registered_on: "Inscrit le "
536 536 label_activity: ActivitΓ©
537 537 label_overall_activity: ActivitΓ© globale
538 538 label_user_activity: "ActivitΓ© de %{value}"
539 539 label_new: Nouveau
540 540 label_logged_as: ConnectΓ© en tant que
541 541 label_environment: Environnement
542 542 label_authentication: Authentification
543 543 label_auth_source: Mode d'authentification
544 544 label_auth_source_new: Nouveau mode d'authentification
545 545 label_auth_source_plural: Modes d'authentification
546 546 label_subproject_plural: Sous-projets
547 547 label_subproject_new: Nouveau sous-projet
548 548 label_and_its_subprojects: "%{value} et ses sous-projets"
549 549 label_min_max_length: Longueurs mini - maxi
550 550 label_list: Liste
551 551 label_date: Date
552 552 label_integer: Entier
553 553 label_float: Nombre dΓ©cimal
554 554 label_boolean: BoolΓ©en
555 555 label_string: Texte
556 556 label_text: Texte long
557 557 label_attribute: Attribut
558 558 label_attribute_plural: Attributs
559 559 label_download: "%{count} tΓ©lΓ©chargement"
560 560 label_download_plural: "%{count} tΓ©lΓ©chargements"
561 561 label_no_data: Aucune donnΓ©e Γ  afficher
562 562 label_change_status: Changer le statut
563 563 label_history: Historique
564 564 label_attachment: Fichier
565 565 label_attachment_new: Nouveau fichier
566 566 label_attachment_delete: Supprimer le fichier
567 567 label_attachment_plural: Fichiers
568 568 label_file_added: Fichier ajoutΓ©
569 569 label_report: Rapport
570 570 label_report_plural: Rapports
571 571 label_news: Annonce
572 572 label_news_new: Nouvelle annonce
573 573 label_news_plural: Annonces
574 574 label_news_latest: Dernières annonces
575 575 label_news_view_all: Voir toutes les annonces
576 576 label_news_added: Annonce ajoutΓ©e
577 577 label_news_comment_added: Commentaire ajoutΓ© Γ  une annonce
578 578 label_settings: Configuration
579 579 label_overview: AperΓ§u
580 580 label_version: Version
581 581 label_version_new: Nouvelle version
582 582 label_version_plural: Versions
583 583 label_confirmation: Confirmation
584 584 label_export_to: 'Formats disponibles :'
585 585 label_read: Lire...
586 586 label_public_projects: Projets publics
587 587 label_open_issues: ouvert
588 588 label_open_issues_plural: ouverts
589 589 label_closed_issues: fermΓ©
590 590 label_closed_issues_plural: fermΓ©s
591 591 label_x_open_issues_abbr_on_total:
592 592 zero: 0 ouverte sur %{total}
593 593 one: 1 ouverte sur %{total}
594 594 other: "%{count} ouvertes sur %{total}"
595 595 label_x_open_issues_abbr:
596 596 zero: 0 ouverte
597 597 one: 1 ouverte
598 598 other: "%{count} ouvertes"
599 599 label_x_closed_issues_abbr:
600 600 zero: 0 fermΓ©e
601 601 one: 1 fermΓ©e
602 602 other: "%{count} fermΓ©es"
603 603 label_x_issues:
604 604 zero: 0 demande
605 605 one: 1 demande
606 606 other: "%{count} demandes"
607 607 label_total: Total
608 608 label_permissions: Permissions
609 609 label_current_status: Statut actuel
610 610 label_new_statuses_allowed: Nouveaux statuts autorisΓ©s
611 611 label_all: tous
612 612 label_none: aucun
613 613 label_nobody: personne
614 614 label_next: Suivant
615 615 label_previous: PrΓ©cΓ©dent
616 616 label_used_by: UtilisΓ© par
617 617 label_details: DΓ©tails
618 618 label_add_note: Ajouter une note
619 619 label_per_page: Par page
620 620 label_calendar: Calendrier
621 621 label_months_from: mois depuis
622 622 label_gantt: Gantt
623 623 label_internal: Interne
624 624 label_last_changes: "%{count} derniers changements"
625 625 label_change_view_all: Voir tous les changements
626 626 label_personalize_page: Personnaliser cette page
627 627 label_comment: Commentaire
628 628 label_comment_plural: Commentaires
629 629 label_x_comments:
630 630 zero: aucun commentaire
631 631 one: un commentaire
632 632 other: "%{count} commentaires"
633 633 label_comment_add: Ajouter un commentaire
634 634 label_comment_added: Commentaire ajoutΓ©
635 635 label_comment_delete: Supprimer les commentaires
636 636 label_query: Rapport personnalisΓ©
637 637 label_query_plural: Rapports personnalisΓ©s
638 638 label_query_new: Nouveau rapport
639 639 label_my_queries: Mes rapports personnalisΓ©s
640 640 label_filter_add: "Ajouter le filtre "
641 641 label_filter_plural: Filtres
642 642 label_equals: Γ©gal
643 643 label_not_equals: diffΓ©rent
644 644 label_in_less_than: dans moins de
645 645 label_in_more_than: dans plus de
646 646 label_in: dans
647 647 label_today: aujourd'hui
648 648 label_all_time: toute la pΓ©riode
649 649 label_yesterday: hier
650 650 label_this_week: cette semaine
651 651 label_last_week: la semaine dernière
652 652 label_last_n_days: "les %{count} derniers jours"
653 653 label_this_month: ce mois-ci
654 654 label_last_month: le mois dernier
655 655 label_this_year: cette annΓ©e
656 656 label_date_range: PΓ©riode
657 657 label_less_than_ago: il y a moins de
658 658 label_more_than_ago: il y a plus de
659 659 label_ago: il y a
660 660 label_contains: contient
661 661 label_not_contains: ne contient pas
662 662 label_day_plural: jours
663 663 label_repository: DΓ©pΓ΄t
664 664 label_repository_new: Nouveau dΓ©pΓ΄t
665 665 label_repository_plural: DΓ©pΓ΄ts
666 666 label_browse: Parcourir
667 667 label_modification: "%{count} modification"
668 668 label_modification_plural: "%{count} modifications"
669 669 label_revision: "RΓ©vision "
670 670 label_revision_plural: RΓ©visions
671 671 label_associated_revisions: RΓ©visions associΓ©es
672 672 label_added: ajoutΓ©
673 673 label_modified: modifiΓ©
674 674 label_copied: copiΓ©
675 675 label_renamed: renommΓ©
676 676 label_deleted: supprimΓ©
677 677 label_latest_revision: Dernière révision
678 678 label_latest_revision_plural: Dernières révisions
679 679 label_view_revisions: Voir les rΓ©visions
680 680 label_max_size: Taille maximale
681 681 label_sort_highest: Remonter en premier
682 682 label_sort_higher: Remonter
683 683 label_sort_lower: Descendre
684 684 label_sort_lowest: Descendre en dernier
685 685 label_roadmap: Roadmap
686 686 label_roadmap_due_in: "Γ‰chΓ©ance dans %{value}"
687 687 label_roadmap_overdue: "En retard de %{value}"
688 688 label_roadmap_no_issues: Aucune demande pour cette version
689 689 label_search: "Recherche "
690 690 label_result_plural: RΓ©sultats
691 691 label_all_words: Tous les mots
692 692 label_wiki: Wiki
693 693 label_wiki_edit: RΓ©vision wiki
694 694 label_wiki_edit_plural: RΓ©visions wiki
695 695 label_wiki_page: Page wiki
696 696 label_wiki_page_plural: Pages wiki
697 697 label_index_by_title: Index par titre
698 698 label_index_by_date: Index par date
699 699 label_current_version: Version actuelle
700 700 label_preview: PrΓ©visualisation
701 701 label_feed_plural: Flux RSS
702 702 label_changes_details: DΓ©tails de tous les changements
703 703 label_issue_tracking: Suivi des demandes
704 704 label_spent_time: Temps passΓ©
705 705 label_f_hour: "%{value} heure"
706 706 label_f_hour_plural: "%{value} heures"
707 707 label_time_tracking: Suivi du temps
708 708 label_change_plural: Changements
709 709 label_statistics: Statistiques
710 710 label_commits_per_month: Commits par mois
711 711 label_commits_per_author: Commits par auteur
712 712 label_view_diff: Voir les diffΓ©rences
713 713 label_diff_inline: en ligne
714 714 label_diff_side_by_side: cΓ΄te Γ  cΓ΄te
715 715 label_options: Options
716 716 label_copy_workflow_from: Copier le workflow de
717 717 label_permissions_report: Synthèse des permissions
718 718 label_watched_issues: Demandes surveillΓ©es
719 719 label_related_issues: Demandes liΓ©es
720 720 label_applied_status: Statut appliquΓ©
721 721 label_loading: Chargement...
722 722 label_relation_new: Nouvelle relation
723 723 label_relation_delete: Supprimer la relation
724 724 label_relates_to: liΓ© Γ 
725 725 label_duplicates: duplique
726 726 label_duplicated_by: dupliquΓ© par
727 727 label_blocks: bloque
728 728 label_blocked_by: bloquΓ© par
729 729 label_precedes: précède
730 730 label_follows: suit
731 731 label_end_to_start: fin Γ  dΓ©but
732 732 label_end_to_end: fin Γ  fin
733 733 label_start_to_start: dΓ©but Γ  dΓ©but
734 734 label_start_to_end: dΓ©but Γ  fin
735 735 label_stay_logged_in: Rester connectΓ©
736 736 label_disabled: dΓ©sactivΓ©
737 737 label_show_completed_versions: Voir les versions passΓ©es
738 738 label_me: moi
739 739 label_board: Forum
740 740 label_board_new: Nouveau forum
741 741 label_board_plural: Forums
742 742 label_topic_plural: Discussions
743 743 label_message_plural: Messages
744 744 label_message_last: Dernier message
745 745 label_message_new: Nouveau message
746 746 label_message_posted: Message ajoutΓ©
747 747 label_reply_plural: RΓ©ponses
748 748 label_send_information: Envoyer les informations Γ  l'utilisateur
749 749 label_year: AnnΓ©e
750 750 label_month: Mois
751 751 label_week: Semaine
752 752 label_date_from: Du
753 753 label_date_to: Au
754 754 label_language_based: BasΓ© sur la langue de l'utilisateur
755 755 label_sort_by: "Trier par %{value}"
756 756 label_send_test_email: Envoyer un email de test
757 757 label_feeds_access_key_created_on: "Clé d'accès RSS créée il y a %{value}"
758 758 label_module_plural: Modules
759 759 label_added_time_by: "AjoutΓ© par %{author} il y a %{age}"
760 760 label_updated_time_by: "Mis Γ  jour par %{author} il y a %{age}"
761 761 label_updated_time: "Mis Γ  jour il y a %{value}"
762 762 label_jump_to_a_project: Aller Γ  un projet...
763 763 label_file_plural: Fichiers
764 764 label_changeset_plural: RΓ©visions
765 765 label_default_columns: Colonnes par dΓ©faut
766 766 label_no_change_option: (Pas de changement)
767 767 label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es
768 768 label_theme: Thème
769 769 label_default: DΓ©faut
770 770 label_search_titles_only: Uniquement dans les titres
771 771 label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets"
772 772 label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..."
773 773 label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue"
774 774 label_registration_activation_by_email: activation du compte par email
775 775 label_registration_manual_activation: activation manuelle du compte
776 776 label_registration_automatic_activation: activation automatique du compte
777 777 label_display_per_page: "Par page : %{value}"
778 778 label_age: Γ‚ge
779 779 label_change_properties: Changer les propriΓ©tΓ©s
780 780 label_general: GΓ©nΓ©ral
781 781 label_more: Plus
782 782 label_scm: SCM
783 783 label_plugins: Plugins
784 784 label_ldap_authentication: Authentification LDAP
785 785 label_downloads_abbr: D/L
786 786 label_optional_description: Description facultative
787 787 label_add_another_file: Ajouter un autre fichier
788 788 label_preferences: PrΓ©fΓ©rences
789 789 label_chronological_order: Dans l'ordre chronologique
790 790 label_reverse_chronological_order: Dans l'ordre chronologique inverse
791 791 label_planning: Planning
792 792 label_incoming_emails: Emails entrants
793 793 label_generate_key: GΓ©nΓ©rer une clΓ©
794 794 label_issue_watchers: Observateurs
795 795 label_example: Exemple
796 796 label_display: Affichage
797 797 label_sort: Tri
798 798 label_ascending: Croissant
799 799 label_descending: DΓ©croissant
800 800 label_date_from_to: Du %{start} au %{end}
801 801 label_wiki_content_added: Page wiki ajoutΓ©e
802 802 label_wiki_content_updated: Page wiki mise Γ  jour
803 803 label_group_plural: Groupes
804 804 label_group: Groupe
805 805 label_group_new: Nouveau groupe
806 806 label_time_entry_plural: Temps passΓ©
807 807 label_version_sharing_none: Non partagΓ©
808 808 label_version_sharing_descendants: Avec les sous-projets
809 809 label_version_sharing_hierarchy: Avec toute la hiΓ©rarchie
810 810 label_version_sharing_tree: Avec tout l'arbre
811 811 label_version_sharing_system: Avec tous les projets
812 812 label_copy_source: Source
813 813 label_copy_target: Cible
814 814 label_copy_same_as_target: Comme la cible
815 815 label_update_issue_done_ratios: Mettre Γ  jour l'avancement des demandes
816 816 label_display_used_statuses_only: N'afficher que les statuts utilisΓ©s dans ce tracker
817 817 label_api_access_key: Clé d'accès API
818 818 label_api_access_key_created_on: Clé d'accès API créée il y a %{value}
819 819 label_feeds_access_key: Clé d'accès RSS
820 820 label_missing_api_access_key: Clé d'accès API manquante
821 821 label_missing_feeds_access_key: Clé d'accès RSS manquante
822 822 label_close_versions: Fermer les versions terminΓ©es
823 823 label_revision_id: RΓ©vision %{value}
824 824 label_profile: Profil
825 825 label_subtask_plural: Sous-tΓ’ches
826 826 label_project_copy_notifications: Envoyer les notifications durant la copie du projet
827 827 label_principal_search: "Rechercher un utilisateur ou un groupe :"
828 828 label_user_search: "Rechercher un utilisateur :"
829 829 label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande
830 830 label_additional_workflow_transitions_for_assignee: Autorisations supplΓ©mentaires lorsque la demande est assignΓ©e Γ  l'utilisateur
831 831 label_issues_visibility_all: Toutes les demandes
832 832 label_issues_visibility_public: Toutes les demandes non privΓ©es
833 833 label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur
834 834 label_export_options: Options d'exportation %{export_format}
835 835 label_copy_attachments: Copier les fichiers
836 836 label_item_position: "%{position} sur %{count}"
837 837 label_completed_versions: Versions passΓ©es
838 838 label_session_expiration: Expiration des sessions
839 839 label_show_closed_projects: Voir les projets fermΓ©s
840 840 label_status_transitions: Changements de statut
841 841 label_fields_permissions: Permissions sur les champs
842 842 label_readonly: Lecture
843 843 label_required: Obligatoire
844 label_attribute_of_project: "%{name} du projet"
845 label_attribute_of_author: "%{name} de l'auteur"
846 label_attribute_of_assigned_to: "%{name} de l'assignΓ©"
847 label_attribute_of_fixed_version: "%{name} de la version cible"
844 848
845 849 button_login: Connexion
846 850 button_submit: Soumettre
847 851 button_save: Sauvegarder
848 852 button_check_all: Tout cocher
849 853 button_uncheck_all: Tout dΓ©cocher
850 854 button_collapse_all: Plier tout
851 855 button_expand_all: DΓ©plier tout
852 856 button_delete: Supprimer
853 857 button_create: CrΓ©er
854 858 button_create_and_continue: CrΓ©er et continuer
855 859 button_test: Tester
856 860 button_edit: Modifier
857 861 button_add: Ajouter
858 862 button_change: Changer
859 863 button_apply: Appliquer
860 864 button_clear: Effacer
861 865 button_lock: Verrouiller
862 866 button_unlock: DΓ©verrouiller
863 867 button_download: TΓ©lΓ©charger
864 868 button_list: Lister
865 869 button_view: Voir
866 870 button_move: DΓ©placer
867 871 button_move_and_follow: DΓ©placer et suivre
868 872 button_back: Retour
869 873 button_cancel: Annuler
870 874 button_activate: Activer
871 875 button_sort: Trier
872 876 button_log_time: Saisir temps
873 877 button_rollback: Revenir Γ  cette version
874 878 button_watch: Surveiller
875 879 button_unwatch: Ne plus surveiller
876 880 button_reply: RΓ©pondre
877 881 button_archive: Archiver
878 882 button_unarchive: DΓ©sarchiver
879 883 button_reset: RΓ©initialiser
880 884 button_rename: Renommer
881 885 button_change_password: Changer de mot de passe
882 886 button_copy: Copier
883 887 button_copy_and_follow: Copier et suivre
884 888 button_annotate: Annoter
885 889 button_update: Mettre Γ  jour
886 890 button_configure: Configurer
887 891 button_quote: Citer
888 892 button_duplicate: Dupliquer
889 893 button_show: Afficher
890 894 button_edit_section: Modifier cette section
891 895 button_export: Exporter
892 896 button_delete_my_account: Supprimer mon compte
893 897 button_close: Fermer
894 898 button_reopen: RΓ©ouvrir
895 899
896 900 status_active: actif
897 901 status_registered: enregistrΓ©
898 902 status_locked: verrouillΓ©
899 903
900 904 project_status_active: actif
901 905 project_status_closed: fermΓ©
902 906 project_status_archived: archivΓ©
903 907
904 908 version_status_open: ouvert
905 909 version_status_locked: verrouillΓ©
906 910 version_status_closed: fermΓ©
907 911
908 912 text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e
909 913 text_regexp_info: ex. ^[A-Z0-9]+$
910 914 text_min_max_length_info: 0 pour aucune restriction
911 915 text_project_destroy_confirmation: Êtes-vous sûr de vouloir supprimer ce projet et toutes ses données ?
912 916 text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront Γ©galement supprimΓ©s."
913 917 text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow
914 918 text_are_you_sure: Êtes-vous sûr ?
915 919 text_tip_issue_begin_day: tΓ’che commenΓ§ant ce jour
916 920 text_tip_issue_end_day: tΓ’che finissant ce jour
917 921 text_tip_issue_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour
918 922 text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et underscore sont autorisΓ©s.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.'
919 923 text_caracters_maximum: "%{count} caractères maximum."
920 924 text_caracters_minimum: "%{count} caractères minimum."
921 925 text_length_between: "Longueur comprise entre %{min} et %{max} caractères."
922 926 text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker
923 927 text_unallowed_characters: Caractères non autorisés
924 928 text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules).
925 929 text_line_separated: Plusieurs valeurs possibles (une valeur par ligne).
926 930 text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits
927 931 text_issue_added: "La demande %{id} a Γ©tΓ© soumise par %{author}."
928 932 text_issue_updated: "La demande %{id} a Γ©tΓ© mise Γ  jour par %{author}."
929 933 text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ?
930 934 text_issue_category_destroy_question: "%{count} demandes sont affectΓ©es Γ  cette catΓ©gorie. Que voulez-vous faire ?"
931 935 text_issue_category_destroy_assignments: N'affecter les demandes Γ  aucune autre catΓ©gorie
932 936 text_issue_category_reassign_to: RΓ©affecter les demandes Γ  cette catΓ©gorie
933 937 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)."
934 938 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Γ©."
935 939 text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut
936 940 text_status_changed_by_changeset: "AppliquΓ© par commit %{value}."
937 941 text_time_logged_by_changeset: "AppliquΓ© par commit %{value}"
938 942 text_issues_destroy_confirmation: 'Êtes-vous sûr de vouloir supprimer la ou les demandes(s) selectionnée(s) ?'
939 943 text_issues_destroy_descendants_confirmation: "Cela entrainera Γ©galement la suppression de %{count} sous-tΓ’che(s)."
940 944 text_select_project_modules: 'SΓ©lectionner les modules Γ  activer pour ce projet :'
941 945 text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ©
942 946 text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture
943 947 text_plugin_assets_writable: RΓ©pertoire public des plugins accessible en Γ©criture
944 948 text_rmagick_available: Bibliothèque RMagick présente (optionnelle)
945 949 text_destroy_time_entries_question: "%{hours} heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ  supprimer. Que voulez-vous faire ?"
946 950 text_destroy_time_entries: Supprimer les heures
947 951 text_assign_time_entries_to_project: Reporter les heures sur le projet
948 952 text_reassign_time_entries: 'Reporter les heures sur cette demande:'
949 953 text_user_wrote: "%{value} a Γ©crit :"
950 954 text_enumeration_destroy_question: "Cette valeur est affectΓ©e Γ  %{count} objets."
951 955 text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ  cette valeur:'
952 956 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."
953 957 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."
954 958 text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.'
955 959 text_custom_field_possible_values_info: 'Une ligne par valeur'
956 960 text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?"
957 961 text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines"
958 962 text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes"
959 963 text_wiki_page_reassign_children: "RΓ©affecter les sous-pages Γ  cette page"
960 964 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 ?"
961 965 text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardΓ© qui sera perdu si vous quittez la page."
962 966 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)"
963 967 text_issue_conflict_resolution_add_notes: "Ajouter mes notes et ignorer mes autres changements"
964 968 text_issue_conflict_resolution_cancel: "Annuler ma mise Γ  jour et rΓ©afficher %{link}"
965 969 text_account_destroy_confirmation: "Êtes-vous sûr de vouloir continuer ?\nVotre compte sera définitivement supprimé, sans aucune possibilité de le réactiver."
966 970 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."
967 971 text_project_closed: Ce projet est fermΓ© et accessible en lecture seule.
968 972
969 973 default_role_manager: "Manager "
970 974 default_role_developer: "DΓ©veloppeur "
971 975 default_role_reporter: "Rapporteur "
972 976 default_tracker_bug: Anomalie
973 977 default_tracker_feature: Evolution
974 978 default_tracker_support: Assistance
975 979 default_issue_status_new: Nouveau
976 980 default_issue_status_in_progress: En cours
977 981 default_issue_status_resolved: RΓ©solu
978 982 default_issue_status_feedback: Commentaire
979 983 default_issue_status_closed: FermΓ©
980 984 default_issue_status_rejected: RejetΓ©
981 985 default_doc_category_user: Documentation utilisateur
982 986 default_doc_category_tech: Documentation technique
983 987 default_priority_low: Bas
984 988 default_priority_normal: Normal
985 989 default_priority_high: Haut
986 990 default_priority_urgent: Urgent
987 991 default_priority_immediate: ImmΓ©diat
988 992 default_activity_design: Conception
989 993 default_activity_development: DΓ©veloppement
990 994
991 995 enumeration_issue_priorities: PrioritΓ©s des demandes
992 996 enumeration_doc_categories: CatΓ©gories des documents
993 997 enumeration_activities: ActivitΓ©s (suivi du temps)
994 998 label_greater_or_equal: ">="
995 999 label_less_or_equal: "<="
996 1000 label_between: entre
997 1001 label_view_all_revisions: Voir toutes les rΓ©visions
998 1002 label_tag: Tag
999 1003 label_branch: Branche
1000 1004 error_no_tracker_in_project: "Aucun tracker n'est associΓ© Γ  ce projet. VΓ©rifier la configuration du projet."
1001 1005 error_no_default_issue_status: "Aucun statut de demande n'est dΓ©fini par dΓ©faut. VΓ©rifier votre configuration (Administration -> Statuts de demandes)."
1002 1006 text_journal_changed: "%{label} changΓ© de %{old} Γ  %{new}"
1003 1007 text_journal_changed_no_detail: "%{label} mis Γ  jour"
1004 1008 text_journal_set_to: "%{label} mis Γ  %{value}"
1005 1009 text_journal_deleted: "%{label} %{old} supprimΓ©"
1006 1010 text_journal_added: "%{label} %{value} ajoutΓ©"
1007 1011 enumeration_system_activity: Activité système
1008 1012 label_board_sticky: Sticky
1009 1013 label_board_locked: VerrouillΓ©
1010 1014 error_unable_delete_issue_status: Impossible de supprimer le statut de demande
1011 1015 error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisΓ©
1012 1016 error_unable_to_connect: Connexion impossible (%{value})
1013 1017 error_can_not_remove_role: Ce rΓ΄le est utilisΓ© et ne peut pas Γͺtre supprimΓ©.
1014 1018 error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas Γͺtre supprimΓ©.
1015 1019 field_principal: Principal
1016 1020 notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}."
1017 1021 text_zoom_out: Zoom arrière
1018 1022 text_zoom_in: Zoom avant
1019 1023 notice_unable_delete_time_entry: Impossible de supprimer le temps passΓ©.
1020 1024 label_overall_spent_time: Temps passΓ© global
1021 1025 field_time_entries: Temps passΓ©
1022 1026 project_module_gantt: Gantt
1023 1027 project_module_calendar: Calendrier
1024 1028 button_edit_associated_wikipage: "Modifier la page wiki associΓ©e: %{page_title}"
1025 1029 text_are_you_sure_with_children: Supprimer la demande et toutes ses sous-demandes ?
1026 1030 field_text: Champ texte
1027 1031 label_user_mail_option_only_owner: Seulement pour ce que j'ai créé
1028 1032 setting_default_notification_option: Option de notification par dΓ©faut
1029 1033 label_user_mail_option_only_my_events: Seulement pour ce que je surveille
1030 1034 label_user_mail_option_only_assigned: Seulement pour ce qui m'est assignΓ©
1031 1035 label_user_mail_option_none: Aucune notification
1032 1036 field_member_of_group: Groupe de l'assignΓ©
1033 1037 field_assigned_to_role: RΓ΄le de l'assignΓ©
1034 1038 setting_emails_header: En-tΓͺte des emails
1035 1039 label_bulk_edit_selected_time_entries: Modifier les temps passΓ©s sΓ©lectionnΓ©s
1036 1040 text_time_entries_destroy_confirmation: "Etes-vous sΓ»r de vouloir supprimer les temps passΓ©s sΓ©lectionnΓ©s ?"
1037 1041 field_scm_path_encoding: Encodage des chemins
1038 1042 text_scm_path_encoding_note: "DΓ©faut : UTF-8"
1039 1043 field_path_to_repository: Chemin du dΓ©pΓ΄t
1040 1044 field_root_directory: RΓ©pertoire racine
1041 1045 field_cvs_module: Module
1042 1046 field_cvsroot: CVSROOT
1043 1047 text_mercurial_repository_note: "DΓ©pΓ΄t local (exemples : /hgrepo, c:\\hgrepo)"
1044 1048 text_scm_command: Commande
1045 1049 text_scm_command_version: Version
1046 1050 label_git_report_last_commit: Afficher le dernier commit des fichiers et rΓ©pertoires
1047 1051 text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification.
1048 1052 text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration.
1049 1053 label_diff: diff
1050 1054 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
1051 1055 description_query_sort_criteria_direction: Ordre de tri
1052 1056 description_project_scope: Périmètre de recherche
1053 1057 description_filter: Filtre
1054 1058 description_user_mail_notification: Option de notification
1055 1059 description_date_from: Date de dΓ©but
1056 1060 description_message_content: Contenu du message
1057 1061 description_available_columns: Colonnes disponibles
1058 1062 description_all_columns: Toutes les colonnes
1059 1063 description_date_range_interval: Choisir une pΓ©riode
1060 1064 description_issue_category_reassign: Choisir une catΓ©gorie
1061 1065 description_search: Champ de recherche
1062 1066 description_notes: Notes
1063 1067 description_date_range_list: Choisir une pΓ©riode prΓ©dΓ©finie
1064 1068 description_choose_project: Projets
1065 1069 description_date_to: Date de fin
1066 1070 description_query_sort_criteria_attribute: Critère de tri
1067 1071 description_wiki_subpages_reassign: Choisir une nouvelle page parent
1068 1072 description_selected_columns: Colonnes sΓ©lectionnΓ©es
1069 1073 label_parent_revision: Parent
1070 1074 label_child_revision: Enfant
1071 1075 error_scm_annotate_big_text_file: Cette entrΓ©e ne peut pas Γͺtre annotΓ©e car elle excΓ¨de la taille maximale.
1072 1076 setting_repositories_encodings: Encodages des fichiers et des dΓ©pΓ΄ts
1073 1077 label_search_for_watchers: Rechercher des observateurs
1074 1078 text_repository_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et underscore sont autorisΓ©s.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.'
@@ -1,583 +1,584
1 1 /* Redmine - project management software
2 2 Copyright (C) 2006-2012 Jean-Philippe Lang */
3 3
4 4 function checkAll(id, checked) {
5 5 if (checked) {
6 6 $('#'+id).find('input[type=checkbox]').attr('checked', true);
7 7 } else {
8 8 $('#'+id).find('input[type=checkbox]').removeAttr('checked');
9 9 }
10 10 }
11 11
12 12 function toggleCheckboxesBySelector(selector) {
13 13 var all_checked = true;
14 14 $(selector).each(function(index) {
15 15 if (!$(this).is(':checked')) { all_checked = false; }
16 16 });
17 17 $(selector).attr('checked', !all_checked)
18 18 }
19 19
20 20 function showAndScrollTo(id, focus) {
21 21 $('#'+id).show();
22 22 if (focus!=null) {
23 23 $('#'+focus).focus();
24 24 }
25 25 $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
26 26 }
27 27
28 28 function toggleRowGroup(el) {
29 29 var tr = $(el).parents('tr').first();
30 30 var n = tr.next();
31 31 tr.toggleClass('open');
32 32 while (n.length && !n.hasClass('group')) {
33 33 n.toggle();
34 34 n = n.next('tr');
35 35 }
36 36 }
37 37
38 38 function collapseAllRowGroups(el) {
39 39 var tbody = $(el).parents('tbody').first();
40 40 tbody.children('tr').each(function(index) {
41 41 if ($(this).hasClass('group')) {
42 42 $(this).removeClass('open');
43 43 } else {
44 44 $(this).hide();
45 45 }
46 46 });
47 47 }
48 48
49 49 function expandAllRowGroups(el) {
50 50 var tbody = $(el).parents('tbody').first();
51 51 tbody.children('tr').each(function(index) {
52 52 if ($(this).hasClass('group')) {
53 53 $(this).addClass('open');
54 54 } else {
55 55 $(this).show();
56 56 }
57 57 });
58 58 }
59 59
60 60 function toggleAllRowGroups(el) {
61 61 var tr = $(el).parents('tr').first();
62 62 if (tr.hasClass('open')) {
63 63 collapseAllRowGroups(el);
64 64 } else {
65 65 expandAllRowGroups(el);
66 66 }
67 67 }
68 68
69 69 function toggleFieldset(el) {
70 70 var fieldset = $(el).parents('fieldset').first();
71 71 fieldset.toggleClass('collapsed');
72 72 fieldset.children('div').toggle();
73 73 }
74 74
75 75 function hideFieldset(el) {
76 76 var fieldset = $(el).parents('fieldset').first();
77 77 fieldset.toggleClass('collapsed');
78 78 fieldset.children('div').hide();
79 79 }
80 80
81 81 function initFilters(){
82 82 $('#add_filter_select').change(function(){
83 83 addFilter($(this).val(), '', []);
84 84 });
85 85 $('#filters-table td.field input[type=checkbox]').each(function(){
86 86 toggleFilter($(this).val());
87 87 });
88 88 $('#filters-table td.field input[type=checkbox]').live('click',function(){
89 89 toggleFilter($(this).val());
90 90 });
91 91 $('#filters-table .toggle-multiselect').live('click',function(){
92 92 toggleMultiSelect($(this).siblings('select'));
93 93 });
94 94 $('#filters-table input[type=text]').live('keypress', function(e){
95 95 if (e.keyCode == 13) submit_query_form("query_form");
96 96 });
97 97 }
98 98
99 99 function addFilter(field, operator, values) {
100 100 var fieldId = field.replace('.', '_');
101 101 var tr = $('#tr_'+fieldId);
102 102 if (tr.length > 0) {
103 103 tr.show();
104 104 } else {
105 105 buildFilterRow(field, operator, values);
106 106 }
107 107 $('#cb_'+fieldId).attr('checked', true);
108 108 toggleFilter(field);
109 109 $('#add_filter_select').val('').children('option').each(function(){
110 110 if ($(this).attr('value') == field) {
111 111 $(this).attr('disabled', true);
112 112 }
113 113 });
114 114 }
115 115
116 116 function buildFilterRow(field, operator, values) {
117 117 var fieldId = field.replace('.', '_');
118 118 var filterTable = $("#filters-table");
119 119 var filterOptions = availableFilters[field];
120 120 var operators = operatorByType[filterOptions['type']];
121 121 var filterValues = filterOptions['values'];
122 122 var i, select;
123 123
124 124 var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html(
125 125 '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' +
126 126 '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' +
127 127 '<td class="values"></td>'
128 128 );
129 129 filterTable.append(tr);
130 130
131 131 select = tr.find('td.operator select');
132 132 for (i=0;i<operators.length;i++){
133 133 var option = $('<option>').val(operators[i]).html(operatorLabels[operators[i]]);
134 134 if (operators[i] == operator) {option.attr('selected', true)};
135 135 select.append(option);
136 136 }
137 137 select.change(function(){toggleOperator(field)});
138 138
139 139 switch (filterOptions['type']){
140 140 case "list":
141 141 case "list_optional":
142 142 case "list_status":
143 143 case "list_subprojects":
144 144 tr.find('td.values').append(
145 145 '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
146 146 ' <span class="toggle-multiselect">&nbsp;</span></span>'
147 147 );
148 148 select = tr.find('td.values select');
149 149 if (values.length > 1) {select.attr('multiple', true)};
150 150 for (i=0;i<filterValues.length;i++){
151 151 var filterValue = filterValues[i];
152 152 var option = $('<option>');
153 153 if ($.isArray(filterValue)) {
154 154 option.val(filterValue[1]).html(filterValue[0]);
155 if (values.indexOf(filterValue[1]) > -1) {option.attr('selected', true)};
155 156 } else {
156 157 option.val(filterValue).html(filterValue);
158 if (values.indexOf(filterValue) > -1) {option.attr('selected', true)};
157 159 }
158 if (values.indexOf(filterValues[i][1]) > -1) {option.attr('selected', true)};
159 160 select.append(option);
160 161 }
161 162 break;
162 163 case "date":
163 164 case "date_past":
164 165 tr.find('td.values').append(
165 166 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" value="'+values[0]+'" /></span>' +
166 167 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" value="'+values[1]+'" /></span>' +
167 168 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" value="'+values[0]+'" /> '+labelDayPlural+'</span>'
168 169 );
169 170 $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions);
170 171 $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions);
171 172 $('#values_'+fieldId).val(values[0]);
172 173 break;
173 174 case "string":
174 175 case "text":
175 176 tr.find('td.values').append(
176 177 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" value="'+values[0]+'" /></span>'
177 178 );
178 179 $('#values_'+fieldId).val(values[0]);
179 180 break;
180 181 case "integer":
181 182 case "float":
182 183 tr.find('td.values').append(
183 184 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" value="'+values[0]+'" /></span>' +
184 185 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" value="'+values[1]+'" /></span>'
185 186 );
186 187 $('#values_'+fieldId+'_1').val(values[0]);
187 188 $('#values_'+fieldId+'_2').val(values[1]);
188 189 break;
189 190 }
190 191 }
191 192
192 193 function toggleFilter(field) {
193 194 var fieldId = field.replace('.', '_');
194 195 if ($('#cb_' + fieldId).is(':checked')) {
195 196 $("#operators_" + fieldId).show().removeAttr('disabled');
196 197 toggleOperator(field);
197 198 } else {
198 199 $("#operators_" + fieldId).hide().attr('disabled', true);
199 200 enableValues(field, []);
200 201 }
201 202 }
202 203
203 204 function enableValues(field, indexes) {
204 205 var fieldId = field.replace('.', '_');
205 206 $('#tr_'+fieldId+' td.values .value').each(function(index) {
206 207 if (indexes.indexOf(index) >= 0) {
207 208 $(this).removeAttr('disabled');
208 209 $(this).parents('span').first().show();
209 210 } else {
210 211 $(this).val('');
211 212 $(this).attr('disabled', true);
212 213 $(this).parents('span').first().hide();
213 214 }
214 215
215 216 if ($(this).hasClass('group')) {
216 217 $(this).addClass('open');
217 218 } else {
218 219 $(this).show();
219 220 }
220 221 });
221 222 }
222 223
223 224 function toggleOperator(field) {
224 225 var fieldId = field.replace('.', '_');
225 226 var operator = $("#operators_" + fieldId);
226 227 switch (operator.val()) {
227 228 case "!*":
228 229 case "*":
229 230 case "t":
230 231 case "w":
231 232 case "o":
232 233 case "c":
233 234 enableValues(field, []);
234 235 break;
235 236 case "><":
236 237 enableValues(field, [0,1]);
237 238 break;
238 239 case "<t+":
239 240 case ">t+":
240 241 case "t+":
241 242 case ">t-":
242 243 case "<t-":
243 244 case "t-":
244 245 enableValues(field, [2]);
245 246 break;
246 247 default:
247 248 enableValues(field, [0]);
248 249 break;
249 250 }
250 251 }
251 252
252 253 function toggleMultiSelect(el) {
253 254 if (el.attr('multiple')) {
254 255 el.removeAttr('multiple');
255 256 } else {
256 257 el.attr('multiple', true);
257 258 }
258 259 }
259 260
260 261 function submit_query_form(id) {
261 262 selectAllOptions("selected_columns");
262 263 $('#'+id).submit();
263 264 }
264 265
265 266 var fileFieldCount = 1;
266 267 function addFileField() {
267 268 var fields = $('#attachments_fields');
268 269 if (fields.children().length >= 10) return false;
269 270 fileFieldCount++;
270 271 var s = fields.children('span').first().clone();
271 272 s.children('input.file').attr('name', "attachments[" + fileFieldCount + "][file]").val('');
272 273 s.children('input.description').attr('name', "attachments[" + fileFieldCount + "][description]").val('');
273 274 fields.append(s);
274 275 }
275 276
276 277 function removeFileField(el) {
277 278 var fields = $('#attachments_fields');
278 279 var s = $(el).parents('span').first();
279 280 if (fields.children().length > 1) {
280 281 s.remove();
281 282 } else {
282 283 s.children('input.file').val('');
283 284 s.children('input.description').val('');
284 285 }
285 286 }
286 287
287 288 function checkFileSize(el, maxSize, message) {
288 289 var files = el.files;
289 290 if (files) {
290 291 for (var i=0; i<files.length; i++) {
291 292 if (files[i].size > maxSize) {
292 293 alert(message);
293 294 el.value = "";
294 295 }
295 296 }
296 297 }
297 298 }
298 299
299 300 function showTab(name) {
300 301 $('div#content .tab-content').hide();
301 302 $('div.tabs a').removeClass('selected');
302 303 $('#tab-content-' + name).show();
303 304 $('#tab-' + name).addClass('selected');
304 305 return false;
305 306 }
306 307
307 308 function moveTabRight(el) {
308 309 var lis = $(el).parents('div.tabs').first().find('ul').children();
309 310 var tabsWidth = 0;
310 311 var i = 0;
311 312 lis.each(function(){
312 313 if ($(this).is(':visible')) {
313 314 tabsWidth += $(this).width() + 6;
314 315 }
315 316 });
316 317 if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; }
317 318 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
318 319 lis.eq(i).hide();
319 320 }
320 321
321 322 function moveTabLeft(el) {
322 323 var lis = $(el).parents('div.tabs').first().find('ul').children();
323 324 var i = 0;
324 325 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
325 326 if (i>0) {
326 327 lis.eq(i-1).show();
327 328 }
328 329 }
329 330
330 331 function displayTabsButtons() {
331 332 var lis;
332 333 var tabsWidth = 0;
333 334 var el;
334 335 $('div.tabs').each(function() {
335 336 el = $(this);
336 337 lis = el.find('ul').children();
337 338 lis.each(function(){
338 339 if ($(this).is(':visible')) {
339 340 tabsWidth += $(this).width() + 6;
340 341 }
341 342 });
342 343 if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) {
343 344 el.find('div.tabs-buttons').hide();
344 345 } else {
345 346 el.find('div.tabs-buttons').show();
346 347 }
347 348 });
348 349 }
349 350
350 351 function setPredecessorFieldsVisibility() {
351 352 var relationType = $('#relation_relation_type');
352 353 if (relationType.val() == "precedes" || relationType.val() == "follows") {
353 354 $('#predecessor_fields').show();
354 355 } else {
355 356 $('#predecessor_fields').hide();
356 357 }
357 358 }
358 359
359 360 function showModal(id, width) {
360 361 var el = $('#'+id).first();
361 362 if (el.length == 0 || el.is(':visible')) {return;}
362 363 var title = el.find('h3.title').text();
363 364 el.dialog({
364 365 width: width,
365 366 modal: true,
366 367 resizable: false,
367 368 dialogClass: 'modal',
368 369 title: title
369 370 });
370 371 el.find("input[type=text], input[type=submit]").first().focus();
371 372 }
372 373
373 374 function hideModal(el) {
374 375 var modal;
375 376 if (el) {
376 377 modal = $(el).parents('.ui-dialog-content');
377 378 } else {
378 379 modal = $('#ajax-modal');
379 380 }
380 381 modal.dialog("close");
381 382 }
382 383
383 384 function submitPreview(url, form, target) {
384 385 $.ajax({
385 386 url: url,
386 387 type: 'post',
387 388 data: $('#'+form).serialize(),
388 389 success: function(data){
389 390 $('#'+target).html(data);
390 391 }
391 392 });
392 393 }
393 394
394 395 function collapseScmEntry(id) {
395 396 $('.'+id).each(function() {
396 397 if ($(this).hasClass('open')) {
397 398 collapseScmEntry($(this).attr('id'));
398 399 }
399 400 $(this).hide();
400 401 });
401 402 $('#'+id).removeClass('open');
402 403 }
403 404
404 405 function expandScmEntry(id) {
405 406 $('.'+id).each(function() {
406 407 $(this).show();
407 408 if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
408 409 expandScmEntry($(this).attr('id'));
409 410 }
410 411 });
411 412 $('#'+id).addClass('open');
412 413 }
413 414
414 415 function scmEntryClick(id, url) {
415 416 el = $('#'+id);
416 417 if (el.hasClass('open')) {
417 418 collapseScmEntry(id);
418 419 el.addClass('collapsed');
419 420 return false;
420 421 } else if (el.hasClass('loaded')) {
421 422 expandScmEntry(id);
422 423 el.removeClass('collapsed');
423 424 return false;
424 425 }
425 426 if (el.hasClass('loading')) {
426 427 return false;
427 428 }
428 429 el.addClass('loading');
429 430 $.ajax({
430 431 url: url,
431 432 success: function(data){
432 433 el.after(data);
433 434 el.addClass('open').addClass('loaded').removeClass('loading');
434 435 }
435 436 });
436 437 return true;
437 438 }
438 439
439 440 function randomKey(size) {
440 441 var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
441 442 var key = '';
442 443 for (i = 0; i < size; i++) {
443 444 key += chars[Math.floor(Math.random() * chars.length)];
444 445 }
445 446 return key;
446 447 }
447 448
448 449 // Can't use Rails' remote select because we need the form data
449 450 function updateIssueFrom(url) {
450 451 $.ajax({
451 452 url: url,
452 453 type: 'post',
453 454 data: $('#issue-form').serialize()
454 455 });
455 456 }
456 457
457 458 function updateBulkEditFrom(url) {
458 459 $.ajax({
459 460 url: url,
460 461 type: 'post',
461 462 data: $('#bulk_edit_form').serialize()
462 463 });
463 464 }
464 465
465 466 function observeAutocompleteField(fieldId, url) {
466 467 $('#'+fieldId).autocomplete({
467 468 source: url,
468 469 minLength: 2,
469 470 });
470 471 }
471 472
472 473 function observeSearchfield(fieldId, targetId, url) {
473 474 $('#'+fieldId).each(function() {
474 475 var $this = $(this);
475 476 $this.attr('data-value-was', $this.val());
476 477 var check = function() {
477 478 var val = $this.val();
478 479 if ($this.attr('data-value-was') != val){
479 480 $this.attr('data-value-was', val);
480 481 if (val != '') {
481 482 $.ajax({
482 483 url: url,
483 484 type: 'get',
484 485 data: {q: $this.val()},
485 486 success: function(data){ $('#'+targetId).html(data); },
486 487 beforeSend: function(){ $this.addClass('ajax-loading'); },
487 488 complete: function(){ $this.removeClass('ajax-loading'); }
488 489 });
489 490 }
490 491 }
491 492 };
492 493 var reset = function() {
493 494 if (timer) {
494 495 clearInterval(timer);
495 496 timer = setInterval(check, 300);
496 497 }
497 498 };
498 499 var timer = setInterval(check, 300);
499 500 $this.bind('keyup click mousemove', reset);
500 501 });
501 502 }
502 503
503 504 function observeProjectModules() {
504 505 var f = function() {
505 506 /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */
506 507 if ($('#project_enabled_module_names_issue_tracking').attr('checked')) {
507 508 $('#project_trackers').show();
508 509 }else{
509 510 $('#project_trackers').hide();
510 511 }
511 512 };
512 513
513 514 $(window).load(f);
514 515 $('#project_enabled_module_names_issue_tracking').change(f);
515 516 }
516 517
517 518 function initMyPageSortable(list, url) {
518 519 $('#list-'+list).sortable({
519 520 connectWith: '.block-receiver',
520 521 tolerance: 'pointer',
521 522 update: function(){
522 523 $.ajax({
523 524 url: url,
524 525 type: 'post',
525 526 data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})}
526 527 });
527 528 }
528 529 });
529 530 $("#list-top, #list-left, #list-right").disableSelection();
530 531 }
531 532
532 533 var warnLeavingUnsavedMessage;
533 534 function warnLeavingUnsaved(message) {
534 535 warnLeavingUnsavedMessage = message;
535 536
536 537 $('form').submit(function(){
537 538 $('textarea').removeData('changed');
538 539 });
539 540 $('textarea').change(function(){
540 541 $(this).data('changed', 'changed');
541 542 });
542 543 window.onbeforeunload = function(){
543 544 var warn = false;
544 545 $('textarea').blur().each(function(){
545 546 if ($(this).data('changed')) {
546 547 warn = true;
547 548 }
548 549 });
549 550 if (warn) {return warnLeavingUnsavedMessage;}
550 551 };
551 552 };
552 553
553 554 $(document).ready(function(){
554 555 $('#ajax-indicator').bind('ajaxSend', function(){
555 556 if ($('.ajax-loading').length == 0) {
556 557 $('#ajax-indicator').show();
557 558 }
558 559 });
559 560 $('#ajax-indicator').bind('ajaxStop', function(){
560 561 $('#ajax-indicator').hide();
561 562 });
562 563 });
563 564
564 565 function hideOnLoad() {
565 566 $('.hol').hide();
566 567 }
567 568
568 569 function addFormObserversForDoubleSubmit() {
569 570 $('form[method=post]').each(function() {
570 571 if (!$(this).hasClass('multiple-submit')) {
571 572 $(this).submit(function(form_submission) {
572 573 if ($(form_submission.target).attr('data-submitted')) {
573 574 form_submission.preventDefault();
574 575 } else {
575 576 $(form_submission.target).attr('data-submitted', true);
576 577 }
577 578 });
578 579 }
579 580 });
580 581 }
581 582
582 583 $(document).ready(hideOnLoad);
583 584 $(document).ready(addFormObserversForDoubleSubmit);
@@ -1,1125 +1,1125
1 1 html {overflow-y:scroll;}
2 2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
3 3
4 4 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
5 5 h1 {margin:0; padding:0; font-size: 24px;}
6 6 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; color: #444;}
7 7 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; color: #444;}
8 8 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
9 9
10 10 /***** Layout *****/
11 11 #wrapper {background: white;}
12 12
13 13 #top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
14 14 #top-menu ul {margin: 0; padding: 0;}
15 15 #top-menu li {
16 16 float:left;
17 17 list-style-type:none;
18 18 margin: 0px 0px 0px 0px;
19 19 padding: 0px 0px 0px 0px;
20 20 white-space:nowrap;
21 21 }
22 22 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
23 23 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
24 24
25 25 #account {float:right;}
26 26
27 27 #header {height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
28 28 #header a {color:#f8f8f8;}
29 29 #header h1 a.ancestor { font-size: 80%; }
30 30 #quick-search {float:right;}
31 31
32 32 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
33 33 #main-menu ul {margin: 0; padding: 0;}
34 34 #main-menu li {
35 35 float:left;
36 36 list-style-type:none;
37 37 margin: 0px 2px 0px 0px;
38 38 padding: 0px 0px 0px 0px;
39 39 white-space:nowrap;
40 40 }
41 41 #main-menu li a {
42 42 display: block;
43 43 color: #fff;
44 44 text-decoration: none;
45 45 font-weight: bold;
46 46 margin: 0;
47 47 padding: 4px 10px 4px 10px;
48 48 }
49 49 #main-menu li a:hover {background:#759FCF; color:#fff;}
50 50 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
51 51
52 52 #admin-menu ul {margin: 0; padding: 0;}
53 53 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
54 54
55 55 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
56 56 #admin-menu a.projects { background-image: url(../images/projects.png); }
57 57 #admin-menu a.users { background-image: url(../images/user.png); }
58 58 #admin-menu a.groups { background-image: url(../images/group.png); }
59 59 #admin-menu a.roles { background-image: url(../images/database_key.png); }
60 60 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
61 61 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
62 62 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
63 63 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
64 64 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
65 65 #admin-menu a.settings { background-image: url(../images/changeset.png); }
66 66 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
67 67 #admin-menu a.info { background-image: url(../images/help.png); }
68 68 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
69 69
70 70 #main {background-color:#EEEEEE;}
71 71
72 72 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
73 73 * html #sidebar{ width: 22%; }
74 74 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
75 75 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
76 76 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
77 77 #sidebar .contextual { margin-right: 1em; }
78 78
79 79 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
80 80 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
81 81 html>body #content { min-height: 600px; }
82 82 * html body #content { height: 600px; } /* IE */
83 83
84 84 #main.nosidebar #sidebar{ display: none; }
85 85 #main.nosidebar #content{ width: auto; border-right: 0; }
86 86
87 87 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
88 88
89 89 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
90 90 #login-form table td {padding: 6px;}
91 91 #login-form label {font-weight: bold;}
92 92 #login-form input#username, #login-form input#password { width: 300px; }
93 93
94 94 div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
95 95 div.modal h3.title {display:none;}
96 96 div.modal p.buttons {text-align:right; margin-bottom:0;}
97 97
98 98 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
99 99
100 100 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
101 101
102 102 /***** Links *****/
103 103 a, a:link, a:visited{ color: #169; text-decoration: none; }
104 104 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
105 105 a img{ border: 0; }
106 106
107 107 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
108 108 a.project.closed, a.project.closed:link, a.project.closed:visited { color: #999; }
109 109
110 110 #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;}
111 111 #sidebar a.selected:hover {text-decoration:none;}
112 112 #admin-menu a {line-height:1.7em;}
113 113 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
114 114
115 115 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
116 116 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
117 117
118 118 a#toggle-completed-versions {color:#999;}
119 119 /***** Tables *****/
120 120 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
121 121 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
122 122 table.list td { vertical-align: top; }
123 123 table.list td.id { width: 2%; text-align: center;}
124 124 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
125 125 table.list td.checkbox input {padding:0px;}
126 126 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
127 127 table.list td.buttons a { padding-right: 0.6em; }
128 128 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
129 129
130 130 tr.project td.name a { white-space:nowrap; }
131 131 tr.project.closed, tr.project.archived { color: #aaa; }
132 132 tr.project.closed a, tr.project.archived a { color: #aaa; }
133 133
134 134 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
135 135 tr.project.idnt-1 td.name {padding-left: 0.5em;}
136 136 tr.project.idnt-2 td.name {padding-left: 2em;}
137 137 tr.project.idnt-3 td.name {padding-left: 3.5em;}
138 138 tr.project.idnt-4 td.name {padding-left: 5em;}
139 139 tr.project.idnt-5 td.name {padding-left: 6.5em;}
140 140 tr.project.idnt-6 td.name {padding-left: 8em;}
141 141 tr.project.idnt-7 td.name {padding-left: 9.5em;}
142 142 tr.project.idnt-8 td.name {padding-left: 11em;}
143 143 tr.project.idnt-9 td.name {padding-left: 12.5em;}
144 144
145 145 tr.issue { text-align: center; white-space: nowrap; }
146 146 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text { white-space: normal; }
147 147 tr.issue td.subject { text-align: left; }
148 148 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
149 149
150 150 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
151 151 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
152 152 tr.issue.idnt-2 td.subject {padding-left: 2em;}
153 153 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
154 154 tr.issue.idnt-4 td.subject {padding-left: 5em;}
155 155 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
156 156 tr.issue.idnt-6 td.subject {padding-left: 8em;}
157 157 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
158 158 tr.issue.idnt-8 td.subject {padding-left: 11em;}
159 159 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
160 160
161 161 tr.entry { border: 1px solid #f8f8f8; }
162 162 tr.entry td { white-space: nowrap; }
163 163 tr.entry td.filename { width: 30%; }
164 164 tr.entry td.filename_no_report { width: 70%; }
165 165 tr.entry td.size { text-align: right; font-size: 90%; }
166 166 tr.entry td.revision, tr.entry td.author { text-align: center; }
167 167 tr.entry td.age { text-align: right; }
168 168 tr.entry.file td.filename a { margin-left: 16px; }
169 169 tr.entry.file td.filename_no_report a { margin-left: 16px; }
170 170
171 171 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
172 172 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
173 173
174 174 tr.changeset { height: 20px }
175 175 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
176 176 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
177 177 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
178 178 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
179 179
180 180 table.files tr.file td { text-align: center; }
181 181 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
182 182 table.files tr.file td.digest { font-size: 80%; }
183 183
184 184 table.members td.roles, table.memberships td.roles { width: 45%; }
185 185
186 186 tr.message { height: 2.6em; }
187 187 tr.message td.subject { padding-left: 20px; }
188 188 tr.message td.created_on { white-space: nowrap; }
189 189 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
190 190 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
191 191 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
192 192
193 193 tr.version.closed, tr.version.closed a { color: #999; }
194 194 tr.version td.name { padding-left: 20px; }
195 195 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
196 196 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
197 197
198 198 tr.user td { width:13%; }
199 199 tr.user td.email { width:18%; }
200 200 tr.user td { white-space: nowrap; }
201 201 tr.user.locked, tr.user.registered { color: #aaa; }
202 202 tr.user.locked a, tr.user.registered a { color: #aaa; }
203 203
204 204 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
205 205
206 206 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
207 207
208 208 tr.time-entry { text-align: center; white-space: nowrap; }
209 209 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
210 210 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
211 211 td.hours .hours-dec { font-size: 0.9em; }
212 212
213 213 table.plugins td { vertical-align: middle; }
214 214 table.plugins td.configure { text-align: right; padding-right: 1em; }
215 215 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
216 216 table.plugins span.description { display: block; font-size: 0.9em; }
217 217 table.plugins span.url { display: block; font-size: 0.9em; }
218 218
219 219 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
220 220 table.list tbody tr.group span.count { color: #aaa; font-size: 80%; }
221 221 tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;}
222 222 tr.group:hover a.toggle-all { display:inline;}
223 223 a.toggle-all:hover {text-decoration:none;}
224 224
225 225 table.list tbody tr:hover { background-color:#ffffdd; }
226 226 table.list tbody tr.group:hover { background-color:inherit; }
227 227 table td {padding:2px;}
228 228 table p {margin:0;}
229 229 .odd {background-color:#f6f7f8;}
230 230 .even {background-color: #fff;}
231 231
232 232 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
233 233 a.sort.asc { background-image: url(../images/sort_asc.png); }
234 234 a.sort.desc { background-image: url(../images/sort_desc.png); }
235 235
236 236 table.attributes { width: 100% }
237 237 table.attributes th { vertical-align: top; text-align: left; }
238 238 table.attributes td { vertical-align: top; }
239 239
240 240 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
241 241 table.boards td.topic-count, table.boards td.message-count {text-align:center;}
242 242 table.boards td.last-message {font-size:80%;}
243 243
244 244 table.messages td.author, table.messages td.created_on, table.messages td.reply-count {text-align:center;}
245 245
246 246 table.query-columns {
247 247 border-collapse: collapse;
248 248 border: 0;
249 249 }
250 250
251 251 table.query-columns td.buttons {
252 252 vertical-align: middle;
253 253 text-align: center;
254 254 }
255 255
256 256 td.center {text-align:center;}
257 257
258 258 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
259 259
260 260 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
261 261 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
262 262 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
263 263 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
264 264
265 265 #watchers ul {margin: 0; padding: 0;}
266 266 #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
267 267 #watchers select {width: 95%; display: block;}
268 268 #watchers a.delete {opacity: 0.4;}
269 269 #watchers a.delete:hover {opacity: 1;}
270 270 #watchers img.gravatar {margin: 0 4px 2px 0;}
271 271
272 272 span#watchers_inputs {overflow:auto; display:block;}
273 273 span.search_for_watchers {display:block;}
274 274 span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
275 275 span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
276 276
277 277
278 278 .highlight { background-color: #FCFD8D;}
279 279 .highlight.token-1 { background-color: #faa;}
280 280 .highlight.token-2 { background-color: #afa;}
281 281 .highlight.token-3 { background-color: #aaf;}
282 282
283 283 .box{
284 284 padding:6px;
285 285 margin-bottom: 10px;
286 286 background-color:#f6f6f6;
287 287 color:#505050;
288 288 line-height:1.5em;
289 289 border: 1px solid #e4e4e4;
290 290 }
291 291
292 292 div.square {
293 293 border: 1px solid #999;
294 294 float: left;
295 295 margin: .3em .4em 0 .4em;
296 296 overflow: hidden;
297 297 width: .6em; height: .6em;
298 298 }
299 299 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
300 300 .contextual input, .contextual select {font-size:0.9em;}
301 301 .message .contextual { margin-top: 0; }
302 302
303 303 .splitcontent {overflow:auto;}
304 304 .splitcontentleft{float:left; width:49%;}
305 305 .splitcontentright{float:right; width:49%;}
306 306 form {display: inline;}
307 307 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
308 308 fieldset {border: 1px solid #e4e4e4; margin:0;}
309 309 legend {color: #484848;}
310 310 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
311 311 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
312 312 blockquote blockquote { margin-left: 0;}
313 313 acronym { border-bottom: 1px dotted; cursor: help; }
314 314 textarea.wiki-edit { width: 99%; }
315 315 li p {margin-top: 0;}
316 316 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
317 317 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
318 318 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
319 319 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
320 320
321 321 div.issue div.subject div div { padding-left: 16px; }
322 322 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
323 323 div.issue div.subject>div>p { margin-top: 0.5em; }
324 324 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
325 325 div.issue span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px;}
326 326 div.issue .next-prev-links {color:#999;}
327 327 div.issue table.attributes th {width:22%;}
328 328 div.issue table.attributes td {width:28%;}
329 329
330 330 #issue_tree table.issues, #relations table.issues { border: 0; }
331 331 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
332 332 #relations td.buttons {padding:0;}
333 333
334 334 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
335 335 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
336 336 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
337 337
338 338 fieldset#date-range p { margin: 2px 0 2px 0; }
339 339 fieldset#filters table { border-collapse: collapse; }
340 340 fieldset#filters table td { padding: 0; vertical-align: middle; }
341 341 fieldset#filters tr.filter { height: 2.1em; }
342 fieldset#filters td.field { width:200px; }
342 fieldset#filters td.field { width:250px; }
343 343 fieldset#filters td.operator { width:170px; }
344 344 fieldset#filters td.values { white-space:nowrap; }
345 345 fieldset#filters td.values select {min-width:130px;}
346 346 fieldset#filters td.values input {height:1em;}
347 347 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
348 348 .toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;}
349 349 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
350 350
351 351 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
352 352 div#issue-changesets div.changeset { padding: 4px;}
353 353 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
354 354 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
355 355
356 356 .journal ul.details img {margin:0 0 -3px 4px;}
357 357
358 358 div#activity dl, #search-results { margin-left: 2em; }
359 359 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
360 360 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
361 361 div#activity dt.me .time { border-bottom: 1px solid #999; }
362 362 div#activity dt .time { color: #777; font-size: 80%; }
363 363 div#activity dd .description, #search-results dd .description { font-style: italic; }
364 364 div#activity span.project:after, #search-results span.project:after { content: " -"; }
365 365 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
366 366
367 367 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
368 368
369 369 div#search-results-counts {float:right;}
370 370 div#search-results-counts ul { margin-top: 0.5em; }
371 371 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
372 372
373 373 dt.issue { background-image: url(../images/ticket.png); }
374 374 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
375 375 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
376 376 dt.issue-note { background-image: url(../images/ticket_note.png); }
377 377 dt.changeset { background-image: url(../images/changeset.png); }
378 378 dt.news { background-image: url(../images/news.png); }
379 379 dt.message { background-image: url(../images/message.png); }
380 380 dt.reply { background-image: url(../images/comments.png); }
381 381 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
382 382 dt.attachment { background-image: url(../images/attachment.png); }
383 383 dt.document { background-image: url(../images/document.png); }
384 384 dt.project { background-image: url(../images/projects.png); }
385 385 dt.time-entry { background-image: url(../images/time.png); }
386 386
387 387 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
388 388
389 389 div#roadmap .related-issues { margin-bottom: 1em; }
390 390 div#roadmap .related-issues td.checkbox { display: none; }
391 391 div#roadmap .wiki h1:first-child { display: none; }
392 392 div#roadmap .wiki h1 { font-size: 120%; }
393 393 div#roadmap .wiki h2 { font-size: 110%; }
394 394 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
395 395
396 396 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
397 397 div#version-summary fieldset { margin-bottom: 1em; }
398 398 div#version-summary fieldset.time-tracking table { width:100%; }
399 399 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
400 400
401 401 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
402 402 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
403 403 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
404 404 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
405 405 table#time-report .hours-dec { font-size: 0.9em; }
406 406
407 407 div.wiki-page .contextual a {opacity: 0.4}
408 408 div.wiki-page .contextual a:hover {opacity: 1}
409 409
410 410 form .attributes select { width: 60%; }
411 411 input#issue_subject { width: 99%; }
412 412 select#issue_done_ratio { width: 95px; }
413 413
414 414 ul.projects { margin: 0; padding-left: 1em; }
415 415 ul.projects.root { margin: 0; padding: 0; }
416 416 ul.projects ul.projects { border-left: 3px solid #e0e0e0; }
417 417 ul.projects li.root { list-style-type:none; margin-bottom: 1em; }
418 418 ul.projects li.child { list-style-type:none; margin-top: 1em;}
419 419 ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
420 420 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
421 421
422 422 #tracker_project_ids ul { margin: 0; padding-left: 1em; }
423 423 #tracker_project_ids li { list-style-type:none; }
424 424
425 425 #related-issues li img {vertical-align:middle;}
426 426
427 427 ul.properties {padding:0; font-size: 0.9em; color: #777;}
428 428 ul.properties li {list-style-type:none;}
429 429 ul.properties li span {font-style:italic;}
430 430
431 431 .total-hours { font-size: 110%; font-weight: bold; }
432 432 .total-hours span.hours-int { font-size: 120%; }
433 433
434 434 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
435 435 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
436 436
437 437 #workflow_copy_form select { width: 200px; }
438 438 table.transitions td.enabled {background: #bfb;}
439 439 table.fields_permissions select {font-size:90%}
440 440 table.fields_permissions td.readonly {background:#ddd;}
441 441 table.fields_permissions td.required {background:#d88;}
442 442
443 443 textarea#custom_field_possible_values {width: 99%}
444 444 input#content_comments {width: 99%}
445 445
446 446 .pagination {font-size: 90%}
447 447 p.pagination {margin-top:8px;}
448 448
449 449 /***** Tabular forms ******/
450 450 .tabular p{
451 451 margin: 0;
452 452 padding: 3px 0 3px 0;
453 453 padding-left: 180px; /* width of left column containing the label elements */
454 454 min-height: 1.8em;
455 455 clear:left;
456 456 }
457 457
458 458 html>body .tabular p {overflow:hidden;}
459 459
460 460 .tabular label{
461 461 font-weight: bold;
462 462 float: left;
463 463 text-align: right;
464 464 /* width of left column */
465 465 margin-left: -180px;
466 466 /* width of labels. Should be smaller than left column to create some right margin */
467 467 width: 175px;
468 468 }
469 469
470 470 .tabular label.floating{
471 471 font-weight: normal;
472 472 margin-left: 0px;
473 473 text-align: left;
474 474 width: 270px;
475 475 }
476 476
477 477 .tabular label.block{
478 478 font-weight: normal;
479 479 margin-left: 0px !important;
480 480 text-align: left;
481 481 float: none;
482 482 display: block;
483 483 width: auto;
484 484 }
485 485
486 486 .tabular label.inline{
487 487 float:none;
488 488 margin-left: 5px !important;
489 489 width: auto;
490 490 }
491 491
492 492 label.no-css {
493 493 font-weight: inherit;
494 494 float:none;
495 495 text-align:left;
496 496 margin-left:0px;
497 497 width:auto;
498 498 }
499 499 input#time_entry_comments { width: 90%;}
500 500
501 501 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
502 502
503 503 .tabular.settings p{ padding-left: 300px; }
504 504 .tabular.settings label{ margin-left: -300px; width: 295px; }
505 505 .tabular.settings textarea { width: 99%; }
506 506
507 507 .settings.enabled_scm table {width:100%}
508 508 .settings.enabled_scm td.scm_name{ font-weight: bold; }
509 509
510 510 fieldset.settings label { display: block; }
511 511 fieldset#notified_events .parent { padding-left: 20px; }
512 512
513 513 span.required {color: #bb0000;}
514 514 .summary {font-style: italic;}
515 515
516 516 #attachments_fields input.description {margin-left: 8px; width:340px;}
517 517 #attachments_fields span {display:block; white-space:nowrap;}
518 518 #attachments_fields img {vertical-align: middle;}
519 519
520 520 div.attachments { margin-top: 12px; }
521 521 div.attachments p { margin:4px 0 2px 0; }
522 522 div.attachments img { vertical-align: middle; }
523 523 div.attachments span.author { font-size: 0.9em; color: #888; }
524 524
525 525 div.thumbnails {margin-top:0.6em;}
526 526 div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;}
527 527 div.thumbnails img {margin: 3px;}
528 528
529 529 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
530 530 .other-formats span + span:before { content: "| "; }
531 531
532 532 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
533 533
534 534 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
535 535 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
536 536
537 537 textarea.text_cf {width:90%;}
538 538
539 539 /* Project members tab */
540 540 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
541 541 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
542 542 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
543 543 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
544 544 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
545 545 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
546 546
547 547 #users_for_watcher {height: 200px; overflow:auto;}
548 548 #users_for_watcher label {display: block;}
549 549
550 550 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
551 551
552 552 input#principal_search, input#user_search {width:100%}
553 553 input#principal_search, input#user_search {
554 554 background: url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px;
555 555 border:1px solid #9EB1C2; border-radius:3px; height:1.5em; width:95%;
556 556 }
557 557 input#principal_search.ajax-loading, input#user_search.ajax-loading {
558 558 background-image: url(../images/loading.gif);
559 559 }
560 560
561 561 * html div#tab-content-members fieldset div { height: 450px; }
562 562
563 563 /***** Flash & error messages ****/
564 564 #errorExplanation, div.flash, .nodata, .warning, .conflict {
565 565 padding: 4px 4px 4px 30px;
566 566 margin-bottom: 12px;
567 567 font-size: 1.1em;
568 568 border: 2px solid;
569 569 }
570 570
571 571 div.flash {margin-top: 8px;}
572 572
573 573 div.flash.error, #errorExplanation {
574 574 background: url(../images/exclamation.png) 8px 50% no-repeat;
575 575 background-color: #ffe3e3;
576 576 border-color: #dd0000;
577 577 color: #880000;
578 578 }
579 579
580 580 div.flash.notice {
581 581 background: url(../images/true.png) 8px 5px no-repeat;
582 582 background-color: #dfffdf;
583 583 border-color: #9fcf9f;
584 584 color: #005f00;
585 585 }
586 586
587 587 div.flash.warning, .conflict {
588 588 background: url(../images/warning.png) 8px 5px no-repeat;
589 589 background-color: #FFEBC1;
590 590 border-color: #FDBF3B;
591 591 color: #A6750C;
592 592 text-align: left;
593 593 }
594 594
595 595 .nodata, .warning {
596 596 text-align: center;
597 597 background-color: #FFEBC1;
598 598 border-color: #FDBF3B;
599 599 color: #A6750C;
600 600 }
601 601
602 602 #errorExplanation ul { font-size: 0.9em;}
603 603 #errorExplanation h2, #errorExplanation p { display: none; }
604 604
605 605 .conflict-details {font-size:80%;}
606 606
607 607 /***** Ajax indicator ******/
608 608 #ajax-indicator {
609 609 position: absolute; /* fixed not supported by IE */
610 610 background-color:#eee;
611 611 border: 1px solid #bbb;
612 612 top:35%;
613 613 left:40%;
614 614 width:20%;
615 615 font-weight:bold;
616 616 text-align:center;
617 617 padding:0.6em;
618 618 z-index:100;
619 619 opacity: 0.5;
620 620 }
621 621
622 622 html>body #ajax-indicator { position: fixed; }
623 623
624 624 #ajax-indicator span {
625 625 background-position: 0% 40%;
626 626 background-repeat: no-repeat;
627 627 background-image: url(../images/loading.gif);
628 628 padding-left: 26px;
629 629 vertical-align: bottom;
630 630 }
631 631
632 632 /***** Calendar *****/
633 633 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
634 634 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
635 635 table.cal thead th.week-number {width: auto;}
636 636 table.cal tbody tr {height: 100px;}
637 637 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
638 638 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
639 639 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
640 640 table.cal td.odd p.day-num {color: #bbb;}
641 641 table.cal td.today {background:#ffffdd;}
642 642 table.cal td.today p.day-num {font-weight: bold;}
643 643 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
644 644 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
645 645 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
646 646 p.cal.legend span {display:block;}
647 647
648 648 /***** Tooltips ******/
649 649 .tooltip{position:relative;z-index:24;}
650 650 .tooltip:hover{z-index:25;color:#000;}
651 651 .tooltip span.tip{display: none; text-align:left;}
652 652
653 653 div.tooltip:hover span.tip{
654 654 display:block;
655 655 position:absolute;
656 656 top:12px; left:24px; width:270px;
657 657 border:1px solid #555;
658 658 background-color:#fff;
659 659 padding: 4px;
660 660 font-size: 0.8em;
661 661 color:#505050;
662 662 }
663 663
664 664 img.ui-datepicker-trigger {
665 665 cursor: pointer;
666 666 vertical-align: middle;
667 667 margin-left: 4px;
668 668 }
669 669
670 670 /***** Progress bar *****/
671 671 table.progress {
672 672 border-collapse: collapse;
673 673 border-spacing: 0pt;
674 674 empty-cells: show;
675 675 text-align: center;
676 676 float:left;
677 677 margin: 1px 6px 1px 0px;
678 678 }
679 679
680 680 table.progress td { height: 1em; }
681 681 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
682 682 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
683 683 table.progress td.todo { background: #eee none repeat scroll 0%; }
684 684 p.pourcent {font-size: 80%;}
685 685 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
686 686
687 687 #roadmap table.progress td { height: 1.2em; }
688 688 /***** Tabs *****/
689 689 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
690 690 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
691 691 #content .tabs ul li {
692 692 float:left;
693 693 list-style-type:none;
694 694 white-space:nowrap;
695 695 margin-right:4px;
696 696 background:#fff;
697 697 position:relative;
698 698 margin-bottom:-1px;
699 699 }
700 700 #content .tabs ul li a{
701 701 display:block;
702 702 font-size: 0.9em;
703 703 text-decoration:none;
704 704 line-height:1.3em;
705 705 padding:4px 6px 4px 6px;
706 706 border: 1px solid #ccc;
707 707 border-bottom: 1px solid #bbbbbb;
708 708 background-color: #f6f6f6;
709 709 color:#999;
710 710 font-weight:bold;
711 711 border-top-left-radius:3px;
712 712 border-top-right-radius:3px;
713 713 }
714 714
715 715 #content .tabs ul li a:hover {
716 716 background-color: #ffffdd;
717 717 text-decoration:none;
718 718 }
719 719
720 720 #content .tabs ul li a.selected {
721 721 background-color: #fff;
722 722 border: 1px solid #bbbbbb;
723 723 border-bottom: 1px solid #fff;
724 724 color:#444;
725 725 }
726 726
727 727 #content .tabs ul li a.selected:hover {background-color: #fff;}
728 728
729 729 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
730 730
731 731 button.tab-left, button.tab-right {
732 732 font-size: 0.9em;
733 733 cursor: pointer;
734 734 height:24px;
735 735 border: 1px solid #ccc;
736 736 border-bottom: 1px solid #bbbbbb;
737 737 position:absolute;
738 738 padding:4px;
739 739 width: 20px;
740 740 bottom: -1px;
741 741 }
742 742
743 743 button.tab-left {
744 744 right: 20px;
745 745 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
746 746 border-top-left-radius:3px;
747 747 }
748 748
749 749 button.tab-right {
750 750 right: 0;
751 751 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
752 752 border-top-right-radius:3px;
753 753 }
754 754
755 755 /***** Diff *****/
756 756 .diff_out { background: #fcc; }
757 757 .diff_out span { background: #faa; }
758 758 .diff_in { background: #cfc; }
759 759 .diff_in span { background: #afa; }
760 760
761 761 .text-diff {
762 762 padding: 1em;
763 763 background-color:#f6f6f6;
764 764 color:#505050;
765 765 border: 1px solid #e4e4e4;
766 766 }
767 767
768 768 /***** Wiki *****/
769 769 div.wiki table {
770 770 border-collapse: collapse;
771 771 margin-bottom: 1em;
772 772 }
773 773
774 774 div.wiki table, div.wiki td, div.wiki th {
775 775 border: 1px solid #bbb;
776 776 padding: 4px;
777 777 }
778 778
779 779 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
780 780
781 781 div.wiki .external {
782 782 background-position: 0% 60%;
783 783 background-repeat: no-repeat;
784 784 padding-left: 12px;
785 785 background-image: url(../images/external.png);
786 786 }
787 787
788 788 div.wiki a.new {color: #b73535;}
789 789
790 790 div.wiki ul, div.wiki ol {margin-bottom:1em;}
791 791
792 792 div.wiki pre {
793 793 margin: 1em 1em 1em 1.6em;
794 794 padding: 8px;
795 795 background-color: #fafafa;
796 796 border: 1px solid #e2e2e2;
797 797 width:auto;
798 798 overflow-x: auto;
799 799 overflow-y: hidden;
800 800 }
801 801
802 802 div.wiki ul.toc {
803 803 background-color: #ffffdd;
804 804 border: 1px solid #e4e4e4;
805 805 padding: 4px;
806 806 line-height: 1.2em;
807 807 margin-bottom: 12px;
808 808 margin-right: 12px;
809 809 margin-left: 0;
810 810 display: table
811 811 }
812 812 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
813 813
814 814 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
815 815 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
816 816 div.wiki ul.toc ul { margin: 0; padding: 0; }
817 817 div.wiki ul.toc li { list-style-type:none; margin: 0;}
818 818 div.wiki ul.toc li li { margin-left: 1.5em; }
819 819 div.wiki ul.toc li li li { font-size: 0.8em; }
820 820 div.wiki ul.toc a {
821 821 font-size: 0.9em;
822 822 font-weight: normal;
823 823 text-decoration: none;
824 824 color: #606060;
825 825 }
826 826 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
827 827
828 828 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
829 829 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
830 830 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
831 831
832 832 div.wiki img { vertical-align: middle; }
833 833
834 834 /***** My page layout *****/
835 835 .block-receiver {
836 836 border:1px dashed #c0c0c0;
837 837 margin-bottom: 20px;
838 838 padding: 15px 0 15px 0;
839 839 }
840 840
841 841 .mypage-box {
842 842 margin:0 0 20px 0;
843 843 color:#505050;
844 844 line-height:1.5em;
845 845 }
846 846
847 847 .handle {cursor: move;}
848 848
849 849 a.close-icon {
850 850 display:block;
851 851 margin-top:3px;
852 852 overflow:hidden;
853 853 width:12px;
854 854 height:12px;
855 855 background-repeat: no-repeat;
856 856 cursor:pointer;
857 857 background-image:url('../images/close.png');
858 858 }
859 859 a.close-icon:hover {background-image:url('../images/close_hl.png');}
860 860
861 861 /***** Gantt chart *****/
862 862 .gantt_hdr {
863 863 position:absolute;
864 864 top:0;
865 865 height:16px;
866 866 border-top: 1px solid #c0c0c0;
867 867 border-bottom: 1px solid #c0c0c0;
868 868 border-right: 1px solid #c0c0c0;
869 869 text-align: center;
870 870 overflow: hidden;
871 871 }
872 872
873 873 .gantt_subjects { font-size: 0.8em; }
874 874 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
875 875
876 876 .task {
877 877 position: absolute;
878 878 height:8px;
879 879 font-size:0.8em;
880 880 color:#888;
881 881 padding:0;
882 882 margin:0;
883 883 line-height:16px;
884 884 white-space:nowrap;
885 885 }
886 886
887 887 .task.label {width:100%;}
888 888 .task.label.project, .task.label.version { font-weight: bold; }
889 889
890 890 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
891 891 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
892 892 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
893 893
894 894 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
895 895 .task_late.parent, .task_done.parent { height: 3px;}
896 896 .task.parent.marker.starting { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;}
897 897 .task.parent.marker.ending { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;}
898 898
899 899 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
900 900 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
901 901 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
902 902 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
903 903
904 904 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
905 905 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
906 906 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
907 907 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
908 908
909 909 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
910 910 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
911 911
912 912 /***** Icons *****/
913 913 .icon {
914 914 background-position: 0% 50%;
915 915 background-repeat: no-repeat;
916 916 padding-left: 20px;
917 917 padding-top: 2px;
918 918 padding-bottom: 3px;
919 919 }
920 920
921 921 .icon-add { background-image: url(../images/add.png); }
922 922 .icon-edit { background-image: url(../images/edit.png); }
923 923 .icon-copy { background-image: url(../images/copy.png); }
924 924 .icon-duplicate { background-image: url(../images/duplicate.png); }
925 925 .icon-del { background-image: url(../images/delete.png); }
926 926 .icon-move { background-image: url(../images/move.png); }
927 927 .icon-save { background-image: url(../images/save.png); }
928 928 .icon-cancel { background-image: url(../images/cancel.png); }
929 929 .icon-multiple { background-image: url(../images/table_multiple.png); }
930 930 .icon-folder { background-image: url(../images/folder.png); }
931 931 .open .icon-folder { background-image: url(../images/folder_open.png); }
932 932 .icon-package { background-image: url(../images/package.png); }
933 933 .icon-user { background-image: url(../images/user.png); }
934 934 .icon-projects { background-image: url(../images/projects.png); }
935 935 .icon-help { background-image: url(../images/help.png); }
936 936 .icon-attachment { background-image: url(../images/attachment.png); }
937 937 .icon-history { background-image: url(../images/history.png); }
938 938 .icon-time { background-image: url(../images/time.png); }
939 939 .icon-time-add { background-image: url(../images/time_add.png); }
940 940 .icon-stats { background-image: url(../images/stats.png); }
941 941 .icon-warning { background-image: url(../images/warning.png); }
942 942 .icon-fav { background-image: url(../images/fav.png); }
943 943 .icon-fav-off { background-image: url(../images/fav_off.png); }
944 944 .icon-reload { background-image: url(../images/reload.png); }
945 945 .icon-lock { background-image: url(../images/locked.png); }
946 946 .icon-unlock { background-image: url(../images/unlock.png); }
947 947 .icon-checked { background-image: url(../images/true.png); }
948 948 .icon-details { background-image: url(../images/zoom_in.png); }
949 949 .icon-report { background-image: url(../images/report.png); }
950 950 .icon-comment { background-image: url(../images/comment.png); }
951 951 .icon-summary { background-image: url(../images/lightning.png); }
952 952 .icon-server-authentication { background-image: url(../images/server_key.png); }
953 953 .icon-issue { background-image: url(../images/ticket.png); }
954 954 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
955 955 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
956 956 .icon-passwd { background-image: url(../images/textfield_key.png); }
957 957 .icon-test { background-image: url(../images/bullet_go.png); }
958 958
959 959 .icon-file { background-image: url(../images/files/default.png); }
960 960 .icon-file.text-plain { background-image: url(../images/files/text.png); }
961 961 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
962 962 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
963 963 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
964 964 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
965 965 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
966 966 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
967 967 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
968 968 .icon-file.text-css { background-image: url(../images/files/css.png); }
969 969 .icon-file.text-html { background-image: url(../images/files/html.png); }
970 970 .icon-file.image-gif { background-image: url(../images/files/image.png); }
971 971 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
972 972 .icon-file.image-png { background-image: url(../images/files/image.png); }
973 973 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
974 974 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
975 975 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
976 976 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
977 977
978 978 img.gravatar {
979 979 padding: 2px;
980 980 border: solid 1px #d5d5d5;
981 981 background: #fff;
982 982 vertical-align: middle;
983 983 }
984 984
985 985 div.issue img.gravatar {
986 986 float: left;
987 987 margin: 0 6px 0 0;
988 988 padding: 5px;
989 989 }
990 990
991 991 div.issue table img.gravatar {
992 992 height: 14px;
993 993 width: 14px;
994 994 padding: 2px;
995 995 float: left;
996 996 margin: 0 0.5em 0 0;
997 997 }
998 998
999 999 h2 img.gravatar {margin: -2px 4px -4px 0;}
1000 1000 h3 img.gravatar {margin: -4px 4px -4px 0;}
1001 1001 h4 img.gravatar {margin: -6px 4px -4px 0;}
1002 1002 td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
1003 1003 #activity dt img.gravatar {float: left; margin: 0 1em 1em 0;}
1004 1004 /* Used on 12px Gravatar img tags without the icon background */
1005 1005 .icon-gravatar {float: left; margin-right: 4px;}
1006 1006
1007 1007 #activity dt, .journal {clear: left;}
1008 1008
1009 1009 .journal-link {float: right;}
1010 1010
1011 1011 h2 img { vertical-align:middle; }
1012 1012
1013 1013 .hascontextmenu { cursor: context-menu; }
1014 1014
1015 1015 /************* CodeRay styles *************/
1016 1016 .syntaxhl div {display: inline;}
1017 1017 .syntaxhl .line-numbers {padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px;}
1018 1018 .syntaxhl .code pre { overflow: auto }
1019 1019 .syntaxhl .debug { color: white !important; background: blue !important; }
1020 1020
1021 1021 .syntaxhl .annotation { color:#007 }
1022 1022 .syntaxhl .attribute-name { color:#b48 }
1023 1023 .syntaxhl .attribute-value { color:#700 }
1024 1024 .syntaxhl .binary { color:#509 }
1025 1025 .syntaxhl .char .content { color:#D20 }
1026 1026 .syntaxhl .char .delimiter { color:#710 }
1027 1027 .syntaxhl .char { color:#D20 }
1028 1028 .syntaxhl .class { color:#258; font-weight:bold }
1029 1029 .syntaxhl .class-variable { color:#369 }
1030 1030 .syntaxhl .color { color:#0A0 }
1031 1031 .syntaxhl .comment { color:#385 }
1032 1032 .syntaxhl .comment .char { color:#385 }
1033 1033 .syntaxhl .comment .delimiter { color:#385 }
1034 1034 .syntaxhl .complex { color:#A08 }
1035 1035 .syntaxhl .constant { color:#258; font-weight:bold }
1036 1036 .syntaxhl .decorator { color:#B0B }
1037 1037 .syntaxhl .definition { color:#099; font-weight:bold }
1038 1038 .syntaxhl .delimiter { color:black }
1039 1039 .syntaxhl .directive { color:#088; font-weight:bold }
1040 1040 .syntaxhl .doc { color:#970 }
1041 1041 .syntaxhl .doc-string { color:#D42; font-weight:bold }
1042 1042 .syntaxhl .doctype { color:#34b }
1043 1043 .syntaxhl .entity { color:#800; font-weight:bold }
1044 1044 .syntaxhl .error { color:#F00; background-color:#FAA }
1045 1045 .syntaxhl .escape { color:#666 }
1046 1046 .syntaxhl .exception { color:#C00; font-weight:bold }
1047 1047 .syntaxhl .float { color:#06D }
1048 1048 .syntaxhl .function { color:#06B; font-weight:bold }
1049 1049 .syntaxhl .global-variable { color:#d70 }
1050 1050 .syntaxhl .hex { color:#02b }
1051 1051 .syntaxhl .imaginary { color:#f00 }
1052 1052 .syntaxhl .include { color:#B44; font-weight:bold }
1053 1053 .syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
1054 1054 .syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
1055 1055 .syntaxhl .instance-variable { color:#33B }
1056 1056 .syntaxhl .integer { color:#06D }
1057 1057 .syntaxhl .key .char { color: #60f }
1058 1058 .syntaxhl .key .delimiter { color: #404 }
1059 1059 .syntaxhl .key { color: #606 }
1060 1060 .syntaxhl .keyword { color:#939; font-weight:bold }
1061 1061 .syntaxhl .label { color:#970; font-weight:bold }
1062 1062 .syntaxhl .local-variable { color:#963 }
1063 1063 .syntaxhl .namespace { color:#707; font-weight:bold }
1064 1064 .syntaxhl .octal { color:#40E }
1065 1065 .syntaxhl .operator { }
1066 1066 .syntaxhl .predefined { color:#369; font-weight:bold }
1067 1067 .syntaxhl .predefined-constant { color:#069 }
1068 1068 .syntaxhl .predefined-type { color:#0a5; font-weight:bold }
1069 1069 .syntaxhl .preprocessor { color:#579 }
1070 1070 .syntaxhl .pseudo-class { color:#00C; font-weight:bold }
1071 1071 .syntaxhl .regexp .content { color:#808 }
1072 1072 .syntaxhl .regexp .delimiter { color:#404 }
1073 1073 .syntaxhl .regexp .modifier { color:#C2C }
1074 1074 .syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
1075 1075 .syntaxhl .reserved { color:#080; font-weight:bold }
1076 1076 .syntaxhl .shell .content { color:#2B2 }
1077 1077 .syntaxhl .shell .delimiter { color:#161 }
1078 1078 .syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
1079 1079 .syntaxhl .string .char { color: #46a }
1080 1080 .syntaxhl .string .content { color: #46a }
1081 1081 .syntaxhl .string .delimiter { color: #46a }
1082 1082 .syntaxhl .string .modifier { color: #46a }
1083 1083 .syntaxhl .symbol .content { color:#d33 }
1084 1084 .syntaxhl .symbol .delimiter { color:#d33 }
1085 1085 .syntaxhl .symbol { color:#d33 }
1086 1086 .syntaxhl .tag { color:#070 }
1087 1087 .syntaxhl .type { color:#339; font-weight:bold }
1088 1088 .syntaxhl .value { color: #088; }
1089 1089 .syntaxhl .variable { color:#037 }
1090 1090
1091 1091 .syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
1092 1092 .syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
1093 1093 .syntaxhl .change { color: #bbf; background: #007; }
1094 1094 .syntaxhl .head { color: #f8f; background: #505 }
1095 1095 .syntaxhl .head .filename { color: white; }
1096 1096
1097 1097 .syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; }
1098 1098 .syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }
1099 1099
1100 1100 .syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
1101 1101 .syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
1102 1102 .syntaxhl .change .change { color: #88f }
1103 1103 .syntaxhl .head .head { color: #f4f }
1104 1104
1105 1105 /***** Media print specific styles *****/
1106 1106 @media print {
1107 1107 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1108 1108 #main { background: #fff; }
1109 1109 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1110 1110 #wiki_add_attachment { display:none; }
1111 1111 .hide-when-print { display: none; }
1112 1112 .autoscroll {overflow-x: visible;}
1113 1113 table.list {margin-top:0.5em;}
1114 1114 table.list th, table.list td {border: 1px solid #aaa;}
1115 1115 }
1116 1116
1117 1117 /* Accessibility specific styles */
1118 1118 .hidden-for-sighted {
1119 1119 position:absolute;
1120 1120 left:-10000px;
1121 1121 top:auto;
1122 1122 width:1px;
1123 1123 height:1px;
1124 1124 overflow:hidden;
1125 1125 }
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,1073 +1,1118
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 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 fixtures :projects, :enabled_modules, :users, :members,
22 22 :member_roles, :roles, :trackers, :issue_statuses,
23 23 :issue_categories, :enumerations, :issues,
24 24 :watchers, :custom_fields, :custom_values, :versions,
25 25 :queries,
26 26 :projects_trackers,
27 27 :custom_fields_trackers
28 28
29 29 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
30 30 query = Query.new(:project => nil, :name => '_')
31 31 assert query.available_filters.has_key?('cf_1')
32 32 assert !query.available_filters.has_key?('cf_3')
33 33 end
34 34
35 35 def test_system_shared_versions_should_be_available_in_global_queries
36 36 Version.find(2).update_attribute :sharing, 'system'
37 37 query = Query.new(:project => nil, :name => '_')
38 38 assert query.available_filters.has_key?('fixed_version_id')
39 39 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
40 40 end
41 41
42 42 def test_project_filter_in_global_queries
43 43 query = Query.new(:project => nil, :name => '_')
44 44 project_filter = query.available_filters["project_id"]
45 45 assert_not_nil project_filter
46 46 project_ids = project_filter[:values].map{|p| p[1]}
47 47 assert project_ids.include?("1") #public project
48 48 assert !project_ids.include?("2") #private project user cannot see
49 49 end
50 50
51 51 def find_issues_with_query(query)
52 52 Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
53 53 query.statement
54 54 ).all
55 55 end
56 56
57 57 def assert_find_issues_with_query_is_successful(query)
58 58 assert_nothing_raised do
59 59 find_issues_with_query(query)
60 60 end
61 61 end
62 62
63 63 def assert_query_statement_includes(query, condition)
64 64 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
65 65 end
66 66
67 67 def assert_query_result(expected, query)
68 68 assert_nothing_raised do
69 69 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
70 70 assert_equal expected.size, query.issue_count
71 71 end
72 72 end
73 73
74 74 def test_query_should_allow_shared_versions_for_a_project_query
75 75 subproject_version = Version.find(4)
76 76 query = Query.new(:project => Project.find(1), :name => '_')
77 77 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
78 78
79 79 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
80 80 end
81 81
82 82 def test_query_with_multiple_custom_fields
83 83 query = Query.find(1)
84 84 assert query.valid?
85 85 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
86 86 issues = find_issues_with_query(query)
87 87 assert_equal 1, issues.length
88 88 assert_equal Issue.find(3), issues.first
89 89 end
90 90
91 91 def test_operator_none
92 92 query = Query.new(:project => Project.find(1), :name => '_')
93 93 query.add_filter('fixed_version_id', '!*', [''])
94 94 query.add_filter('cf_1', '!*', [''])
95 95 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
96 96 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
97 97 find_issues_with_query(query)
98 98 end
99 99
100 100 def test_operator_none_for_integer
101 101 query = Query.new(:project => Project.find(1), :name => '_')
102 102 query.add_filter('estimated_hours', '!*', [''])
103 103 issues = find_issues_with_query(query)
104 104 assert !issues.empty?
105 105 assert issues.all? {|i| !i.estimated_hours}
106 106 end
107 107
108 108 def test_operator_none_for_date
109 109 query = Query.new(:project => Project.find(1), :name => '_')
110 110 query.add_filter('start_date', '!*', [''])
111 111 issues = find_issues_with_query(query)
112 112 assert !issues.empty?
113 113 assert issues.all? {|i| i.start_date.nil?}
114 114 end
115 115
116 116 def test_operator_none_for_string_custom_field
117 117 query = Query.new(:project => Project.find(1), :name => '_')
118 118 query.add_filter('cf_2', '!*', [''])
119 119 assert query.has_filter?('cf_2')
120 120 issues = find_issues_with_query(query)
121 121 assert !issues.empty?
122 122 assert issues.all? {|i| i.custom_field_value(2).blank?}
123 123 end
124 124
125 125 def test_operator_all
126 126 query = Query.new(:project => Project.find(1), :name => '_')
127 127 query.add_filter('fixed_version_id', '*', [''])
128 128 query.add_filter('cf_1', '*', [''])
129 129 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
130 130 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
131 131 find_issues_with_query(query)
132 132 end
133 133
134 134 def test_operator_all_for_date
135 135 query = Query.new(:project => Project.find(1), :name => '_')
136 136 query.add_filter('start_date', '*', [''])
137 137 issues = find_issues_with_query(query)
138 138 assert !issues.empty?
139 139 assert issues.all? {|i| i.start_date.present?}
140 140 end
141 141
142 142 def test_operator_all_for_string_custom_field
143 143 query = Query.new(:project => Project.find(1), :name => '_')
144 144 query.add_filter('cf_2', '*', [''])
145 145 assert query.has_filter?('cf_2')
146 146 issues = find_issues_with_query(query)
147 147 assert !issues.empty?
148 148 assert issues.all? {|i| i.custom_field_value(2).present?}
149 149 end
150 150
151 151 def test_numeric_filter_should_not_accept_non_numeric_values
152 152 query = Query.new(:name => '_')
153 153 query.add_filter('estimated_hours', '=', ['a'])
154 154
155 155 assert query.has_filter?('estimated_hours')
156 156 assert !query.valid?
157 157 end
158 158
159 159 def test_operator_is_on_float
160 160 Issue.update_all("estimated_hours = 171.2", "id=2")
161 161
162 162 query = Query.new(:name => '_')
163 163 query.add_filter('estimated_hours', '=', ['171.20'])
164 164 issues = find_issues_with_query(query)
165 165 assert_equal 1, issues.size
166 166 assert_equal 2, issues.first.id
167 167 end
168 168
169 169 def test_operator_is_on_integer_custom_field
170 170 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
171 171 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
172 172 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
173 173 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
174 174
175 175 query = Query.new(:name => '_')
176 176 query.add_filter("cf_#{f.id}", '=', ['12'])
177 177 issues = find_issues_with_query(query)
178 178 assert_equal 1, issues.size
179 179 assert_equal 2, issues.first.id
180 180 end
181 181
182 182 def test_operator_is_on_integer_custom_field_should_accept_negative_value
183 183 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
184 184 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
185 185 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
186 186 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
187 187
188 188 query = Query.new(:name => '_')
189 189 query.add_filter("cf_#{f.id}", '=', ['-12'])
190 190 assert query.valid?
191 191 issues = find_issues_with_query(query)
192 192 assert_equal 1, issues.size
193 193 assert_equal 2, issues.first.id
194 194 end
195 195
196 196 def test_operator_is_on_float_custom_field
197 197 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
198 198 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
199 199 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
200 200 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
201 201
202 202 query = Query.new(:name => '_')
203 203 query.add_filter("cf_#{f.id}", '=', ['12.7'])
204 204 issues = find_issues_with_query(query)
205 205 assert_equal 1, issues.size
206 206 assert_equal 2, issues.first.id
207 207 end
208 208
209 209 def test_operator_is_on_float_custom_field_should_accept_negative_value
210 210 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
211 211 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
212 212 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
213 213 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
214 214
215 215 query = Query.new(:name => '_')
216 216 query.add_filter("cf_#{f.id}", '=', ['-12.7'])
217 217 assert query.valid?
218 218 issues = find_issues_with_query(query)
219 219 assert_equal 1, issues.size
220 220 assert_equal 2, issues.first.id
221 221 end
222 222
223 223 def test_operator_is_on_multi_list_custom_field
224 224 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
225 225 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
226 226 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
227 227 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
228 228 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
229 229
230 230 query = Query.new(:name => '_')
231 231 query.add_filter("cf_#{f.id}", '=', ['value1'])
232 232 issues = find_issues_with_query(query)
233 233 assert_equal [1, 3], issues.map(&:id).sort
234 234
235 235 query = Query.new(:name => '_')
236 236 query.add_filter("cf_#{f.id}", '=', ['value2'])
237 237 issues = find_issues_with_query(query)
238 238 assert_equal [1], issues.map(&:id).sort
239 239 end
240 240
241 241 def test_operator_is_not_on_multi_list_custom_field
242 242 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
243 243 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
244 244 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
245 245 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
246 246 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
247 247
248 248 query = Query.new(:name => '_')
249 249 query.add_filter("cf_#{f.id}", '!', ['value1'])
250 250 issues = find_issues_with_query(query)
251 251 assert !issues.map(&:id).include?(1)
252 252 assert !issues.map(&:id).include?(3)
253 253
254 254 query = Query.new(:name => '_')
255 255 query.add_filter("cf_#{f.id}", '!', ['value2'])
256 256 issues = find_issues_with_query(query)
257 257 assert !issues.map(&:id).include?(1)
258 258 assert issues.map(&:id).include?(3)
259 259 end
260 260
261 261 def test_operator_is_on_is_private_field
262 262 # is_private filter only available for those who can set issues private
263 263 User.current = User.find(2)
264 264
265 265 query = Query.new(:name => '_')
266 266 assert query.available_filters.key?('is_private')
267 267
268 268 query.add_filter("is_private", '=', ['1'])
269 269 issues = find_issues_with_query(query)
270 270 assert issues.any?
271 271 assert_nil issues.detect {|issue| !issue.is_private?}
272 272 ensure
273 273 User.current = nil
274 274 end
275 275
276 276 def test_operator_is_not_on_is_private_field
277 277 # is_private filter only available for those who can set issues private
278 278 User.current = User.find(2)
279 279
280 280 query = Query.new(:name => '_')
281 281 assert query.available_filters.key?('is_private')
282 282
283 283 query.add_filter("is_private", '!', ['1'])
284 284 issues = find_issues_with_query(query)
285 285 assert issues.any?
286 286 assert_nil issues.detect {|issue| issue.is_private?}
287 287 ensure
288 288 User.current = nil
289 289 end
290 290
291 291 def test_operator_greater_than
292 292 query = Query.new(:project => Project.find(1), :name => '_')
293 293 query.add_filter('done_ratio', '>=', ['40'])
294 294 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
295 295 find_issues_with_query(query)
296 296 end
297 297
298 298 def test_operator_greater_than_a_float
299 299 query = Query.new(:project => Project.find(1), :name => '_')
300 300 query.add_filter('estimated_hours', '>=', ['40.5'])
301 301 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
302 302 find_issues_with_query(query)
303 303 end
304 304
305 305 def test_operator_greater_than_on_int_custom_field
306 306 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
307 307 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
308 308 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
309 309 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
310 310
311 311 query = Query.new(:project => Project.find(1), :name => '_')
312 312 query.add_filter("cf_#{f.id}", '>=', ['8'])
313 313 issues = find_issues_with_query(query)
314 314 assert_equal 1, issues.size
315 315 assert_equal 2, issues.first.id
316 316 end
317 317
318 318 def test_operator_lesser_than
319 319 query = Query.new(:project => Project.find(1), :name => '_')
320 320 query.add_filter('done_ratio', '<=', ['30'])
321 321 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
322 322 find_issues_with_query(query)
323 323 end
324 324
325 325 def test_operator_lesser_than_on_custom_field
326 326 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
327 327 query = Query.new(:project => Project.find(1), :name => '_')
328 328 query.add_filter("cf_#{f.id}", '<=', ['30'])
329 329 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0")
330 330 find_issues_with_query(query)
331 331 end
332 332
333 333 def test_operator_between
334 334 query = Query.new(:project => Project.find(1), :name => '_')
335 335 query.add_filter('done_ratio', '><', ['30', '40'])
336 336 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
337 337 find_issues_with_query(query)
338 338 end
339 339
340 340 def test_operator_between_on_custom_field
341 341 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
342 342 query = Query.new(:project => Project.find(1), :name => '_')
343 343 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
344 344 assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement
345 345 find_issues_with_query(query)
346 346 end
347 347
348 348 def test_date_filter_should_not_accept_non_date_values
349 349 query = Query.new(:name => '_')
350 350 query.add_filter('created_on', '=', ['a'])
351 351
352 352 assert query.has_filter?('created_on')
353 353 assert !query.valid?
354 354 end
355 355
356 356 def test_date_filter_should_not_accept_invalid_date_values
357 357 query = Query.new(:name => '_')
358 358 query.add_filter('created_on', '=', ['2011-01-34'])
359 359
360 360 assert query.has_filter?('created_on')
361 361 assert !query.valid?
362 362 end
363 363
364 364 def test_relative_date_filter_should_not_accept_non_integer_values
365 365 query = Query.new(:name => '_')
366 366 query.add_filter('created_on', '>t-', ['a'])
367 367
368 368 assert query.has_filter?('created_on')
369 369 assert !query.valid?
370 370 end
371 371
372 372 def test_operator_date_equals
373 373 query = Query.new(:name => '_')
374 374 query.add_filter('due_date', '=', ['2011-07-10'])
375 375 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
376 376 find_issues_with_query(query)
377 377 end
378 378
379 379 def test_operator_date_lesser_than
380 380 query = Query.new(:name => '_')
381 381 query.add_filter('due_date', '<=', ['2011-07-10'])
382 382 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
383 383 find_issues_with_query(query)
384 384 end
385 385
386 386 def test_operator_date_greater_than
387 387 query = Query.new(:name => '_')
388 388 query.add_filter('due_date', '>=', ['2011-07-10'])
389 389 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
390 390 find_issues_with_query(query)
391 391 end
392 392
393 393 def test_operator_date_between
394 394 query = Query.new(:name => '_')
395 395 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
396 396 assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
397 397 find_issues_with_query(query)
398 398 end
399 399
400 400 def test_operator_in_more_than
401 401 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
402 402 query = Query.new(:project => Project.find(1), :name => '_')
403 403 query.add_filter('due_date', '>t+', ['15'])
404 404 issues = find_issues_with_query(query)
405 405 assert !issues.empty?
406 406 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
407 407 end
408 408
409 409 def test_operator_in_less_than
410 410 query = Query.new(:project => Project.find(1), :name => '_')
411 411 query.add_filter('due_date', '<t+', ['15'])
412 412 issues = find_issues_with_query(query)
413 413 assert !issues.empty?
414 414 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
415 415 end
416 416
417 417 def test_operator_less_than_ago
418 418 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
419 419 query = Query.new(:project => Project.find(1), :name => '_')
420 420 query.add_filter('due_date', '>t-', ['3'])
421 421 issues = find_issues_with_query(query)
422 422 assert !issues.empty?
423 423 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
424 424 end
425 425
426 426 def test_operator_more_than_ago
427 427 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
428 428 query = Query.new(:project => Project.find(1), :name => '_')
429 429 query.add_filter('due_date', '<t-', ['10'])
430 430 assert query.statement.include?("#{Issue.table_name}.due_date <=")
431 431 issues = find_issues_with_query(query)
432 432 assert !issues.empty?
433 433 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
434 434 end
435 435
436 436 def test_operator_in
437 437 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
438 438 query = Query.new(:project => Project.find(1), :name => '_')
439 439 query.add_filter('due_date', 't+', ['2'])
440 440 issues = find_issues_with_query(query)
441 441 assert !issues.empty?
442 442 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
443 443 end
444 444
445 445 def test_operator_ago
446 446 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
447 447 query = Query.new(:project => Project.find(1), :name => '_')
448 448 query.add_filter('due_date', 't-', ['3'])
449 449 issues = find_issues_with_query(query)
450 450 assert !issues.empty?
451 451 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
452 452 end
453 453
454 454 def test_operator_today
455 455 query = Query.new(:project => Project.find(1), :name => '_')
456 456 query.add_filter('due_date', 't', [''])
457 457 issues = find_issues_with_query(query)
458 458 assert !issues.empty?
459 459 issues.each {|issue| assert_equal Date.today, issue.due_date}
460 460 end
461 461
462 462 def test_operator_this_week_on_date
463 463 query = Query.new(:project => Project.find(1), :name => '_')
464 464 query.add_filter('due_date', 'w', [''])
465 465 find_issues_with_query(query)
466 466 end
467 467
468 468 def test_operator_this_week_on_datetime
469 469 query = Query.new(:project => Project.find(1), :name => '_')
470 470 query.add_filter('created_on', 'w', [''])
471 471 find_issues_with_query(query)
472 472 end
473 473
474 474 def test_operator_contains
475 475 query = Query.new(:project => Project.find(1), :name => '_')
476 476 query.add_filter('subject', '~', ['uNable'])
477 477 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
478 478 result = find_issues_with_query(query)
479 479 assert result.empty?
480 480 result.each {|issue| assert issue.subject.downcase.include?('unable') }
481 481 end
482 482
483 483 def test_range_for_this_week_with_week_starting_on_monday
484 484 I18n.locale = :fr
485 485 assert_equal '1', I18n.t(:general_first_day_of_week)
486 486
487 487 Date.stubs(:today).returns(Date.parse('2011-04-29'))
488 488
489 489 query = Query.new(:project => Project.find(1), :name => '_')
490 490 query.add_filter('due_date', 'w', [''])
491 491 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
492 492 I18n.locale = :en
493 493 end
494 494
495 495 def test_range_for_this_week_with_week_starting_on_sunday
496 496 I18n.locale = :en
497 497 assert_equal '7', I18n.t(:general_first_day_of_week)
498 498
499 499 Date.stubs(:today).returns(Date.parse('2011-04-29'))
500 500
501 501 query = Query.new(:project => Project.find(1), :name => '_')
502 502 query.add_filter('due_date', 'w', [''])
503 503 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
504 504 end
505 505
506 506 def test_operator_does_not_contains
507 507 query = Query.new(:project => Project.find(1), :name => '_')
508 508 query.add_filter('subject', '!~', ['uNable'])
509 509 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
510 510 find_issues_with_query(query)
511 511 end
512 512
513 513 def test_filter_assigned_to_me
514 514 user = User.find(2)
515 515 group = Group.find(10)
516 516 User.current = user
517 517 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
518 518 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
519 519 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
520 520 group.users << user
521 521
522 522 query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
523 523 result = query.issues
524 524 assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id)
525 525
526 526 assert result.include?(i1)
527 527 assert result.include?(i2)
528 528 assert !result.include?(i3)
529 529 end
530 530
531 531 def test_user_custom_field_filtered_on_me
532 532 User.current = User.find(2)
533 533 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
534 534 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
535 535 issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
536 536
537 537 query = Query.new(:name => '_', :project => Project.find(1))
538 538 filter = query.available_filters["cf_#{cf.id}"]
539 539 assert_not_nil filter
540 540 assert_include 'me', filter[:values].map{|v| v[1]}
541 541
542 542 query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
543 543 result = query.issues
544 544 assert_equal 1, result.size
545 545 assert_equal issue1, result.first
546 546 end
547 547
548 548 def test_filter_my_projects
549 549 User.current = User.find(2)
550 550 query = Query.new(:name => '_')
551 551 filter = query.available_filters['project_id']
552 552 assert_not_nil filter
553 553 assert_include 'mine', filter[:values].map{|v| v[1]}
554 554
555 555 query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
556 556 result = query.issues
557 557 assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
558 558 end
559 559
560 560 def test_filter_watched_issues
561 561 User.current = User.find(1)
562 562 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
563 563 result = find_issues_with_query(query)
564 564 assert_not_nil result
565 565 assert !result.empty?
566 566 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
567 567 User.current = nil
568 568 end
569 569
570 570 def test_filter_unwatched_issues
571 571 User.current = User.find(1)
572 572 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
573 573 result = find_issues_with_query(query)
574 574 assert_not_nil result
575 575 assert !result.empty?
576 576 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
577 577 User.current = nil
578 578 end
579 579
580 def test_filter_on_project_custom_field
581 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
582 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
583 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
584
585 query = Query.new(:name => '_')
586 filter_name = "project.cf_#{field.id}"
587 assert_include filter_name, query.available_filters.keys
588 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
589 assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
590 end
591
592 def test_filter_on_author_custom_field
593 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
594 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
595
596 query = Query.new(:name => '_')
597 filter_name = "author.cf_#{field.id}"
598 assert_include filter_name, query.available_filters.keys
599 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
600 assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
601 end
602
603 def test_filter_on_assigned_to_custom_field
604 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
605 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
606
607 query = Query.new(:name => '_')
608 filter_name = "assigned_to.cf_#{field.id}"
609 assert_include filter_name, query.available_filters.keys
610 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
611 assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
612 end
613
614 def test_filter_on_fixed_version_custom_field
615 field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
616 CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
617
618 query = Query.new(:name => '_')
619 filter_name = "fixed_version.cf_#{field.id}"
620 assert_include filter_name, query.available_filters.keys
621 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
622 assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
623 end
624
580 625 def test_statement_should_be_nil_with_no_filters
581 626 q = Query.new(:name => '_')
582 627 q.filters = {}
583 628
584 629 assert q.valid?
585 630 assert_nil q.statement
586 631 end
587 632
588 633 def test_default_columns
589 634 q = Query.new
590 635 assert !q.columns.empty?
591 636 end
592 637
593 638 def test_set_column_names
594 639 q = Query.new
595 640 q.column_names = ['tracker', :subject, '', 'unknonw_column']
596 641 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
597 642 c = q.columns.first
598 643 assert q.has_column?(c)
599 644 end
600 645
601 646 def test_query_should_preload_spent_hours
602 647 q = Query.new(:name => '_', :column_names => [:subject, :spent_hours])
603 648 assert q.has_column?(:spent_hours)
604 649 issues = q.issues
605 650 assert_not_nil issues.first.instance_variable_get("@spent_hours")
606 651 end
607 652
608 653 def test_groupable_columns_should_include_custom_fields
609 654 q = Query.new
610 655 column = q.groupable_columns.detect {|c| c.name == :cf_1}
611 656 assert_not_nil column
612 657 assert_kind_of QueryCustomFieldColumn, column
613 658 end
614 659
615 660 def test_groupable_columns_should_not_include_multi_custom_fields
616 661 field = CustomField.find(1)
617 662 field.update_attribute :multiple, true
618 663
619 664 q = Query.new
620 665 column = q.groupable_columns.detect {|c| c.name == :cf_1}
621 666 assert_nil column
622 667 end
623 668
624 669 def test_groupable_columns_should_include_user_custom_fields
625 670 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
626 671
627 672 q = Query.new
628 673 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
629 674 end
630 675
631 676 def test_groupable_columns_should_include_version_custom_fields
632 677 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
633 678
634 679 q = Query.new
635 680 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
636 681 end
637 682
638 683 def test_grouped_with_valid_column
639 684 q = Query.new(:group_by => 'status')
640 685 assert q.grouped?
641 686 assert_not_nil q.group_by_column
642 687 assert_equal :status, q.group_by_column.name
643 688 assert_not_nil q.group_by_statement
644 689 assert_equal 'status', q.group_by_statement
645 690 end
646 691
647 692 def test_grouped_with_invalid_column
648 693 q = Query.new(:group_by => 'foo')
649 694 assert !q.grouped?
650 695 assert_nil q.group_by_column
651 696 assert_nil q.group_by_statement
652 697 end
653 698
654 699 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
655 700 with_settings :user_format => 'lastname_coma_firstname' do
656 701 q = Query.new
657 702 assert q.sortable_columns.has_key?('assigned_to')
658 703 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
659 704 end
660 705 end
661 706
662 707 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
663 708 with_settings :user_format => 'lastname_coma_firstname' do
664 709 q = Query.new
665 710 assert q.sortable_columns.has_key?('author')
666 711 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
667 712 end
668 713 end
669 714
670 715 def test_sortable_columns_should_include_custom_field
671 716 q = Query.new
672 717 assert q.sortable_columns['cf_1']
673 718 end
674 719
675 720 def test_sortable_columns_should_not_include_multi_custom_field
676 721 field = CustomField.find(1)
677 722 field.update_attribute :multiple, true
678 723
679 724 q = Query.new
680 725 assert !q.sortable_columns['cf_1']
681 726 end
682 727
683 728 def test_default_sort
684 729 q = Query.new
685 730 assert_equal [], q.sort_criteria
686 731 end
687 732
688 733 def test_set_sort_criteria_with_hash
689 734 q = Query.new
690 735 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
691 736 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
692 737 end
693 738
694 739 def test_set_sort_criteria_with_array
695 740 q = Query.new
696 741 q.sort_criteria = [['priority', 'desc'], 'tracker']
697 742 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
698 743 end
699 744
700 745 def test_create_query_with_sort
701 746 q = Query.new(:name => 'Sorted')
702 747 q.sort_criteria = [['priority', 'desc'], 'tracker']
703 748 assert q.save
704 749 q.reload
705 750 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
706 751 end
707 752
708 753 def test_sort_by_string_custom_field_asc
709 754 q = Query.new
710 755 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
711 756 assert c
712 757 assert c.sortable
713 758 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
714 759 q.statement
715 760 ).order("#{c.sortable} ASC").all
716 761 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
717 762 assert !values.empty?
718 763 assert_equal values.sort, values
719 764 end
720 765
721 766 def test_sort_by_string_custom_field_desc
722 767 q = Query.new
723 768 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
724 769 assert c
725 770 assert c.sortable
726 771 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
727 772 q.statement
728 773 ).order("#{c.sortable} DESC").all
729 774 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
730 775 assert !values.empty?
731 776 assert_equal values.sort.reverse, values
732 777 end
733 778
734 779 def test_sort_by_float_custom_field_asc
735 780 q = Query.new
736 781 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
737 782 assert c
738 783 assert c.sortable
739 784 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
740 785 q.statement
741 786 ).order("#{c.sortable} ASC").all
742 787 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
743 788 assert !values.empty?
744 789 assert_equal values.sort, values
745 790 end
746 791
747 792 def test_invalid_query_should_raise_query_statement_invalid_error
748 793 q = Query.new
749 794 assert_raise Query::StatementInvalid do
750 795 q.issues(:conditions => "foo = 1")
751 796 end
752 797 end
753 798
754 799 def test_issue_count
755 800 q = Query.new(:name => '_')
756 801 issue_count = q.issue_count
757 802 assert_equal q.issues.size, issue_count
758 803 end
759 804
760 805 def test_issue_count_with_archived_issues
761 806 p = Project.generate! do |project|
762 807 project.status = Project::STATUS_ARCHIVED
763 808 end
764 809 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
765 810 assert !i.visible?
766 811
767 812 test_issue_count
768 813 end
769 814
770 815 def test_issue_count_by_association_group
771 816 q = Query.new(:name => '_', :group_by => 'assigned_to')
772 817 count_by_group = q.issue_count_by_group
773 818 assert_kind_of Hash, count_by_group
774 819 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
775 820 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
776 821 assert count_by_group.has_key?(User.find(3))
777 822 end
778 823
779 824 def test_issue_count_by_list_custom_field_group
780 825 q = Query.new(:name => '_', :group_by => 'cf_1')
781 826 count_by_group = q.issue_count_by_group
782 827 assert_kind_of Hash, count_by_group
783 828 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
784 829 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
785 830 assert count_by_group.has_key?('MySQL')
786 831 end
787 832
788 833 def test_issue_count_by_date_custom_field_group
789 834 q = Query.new(:name => '_', :group_by => 'cf_8')
790 835 count_by_group = q.issue_count_by_group
791 836 assert_kind_of Hash, count_by_group
792 837 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
793 838 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
794 839 end
795 840
796 841 def test_issue_count_with_nil_group_only
797 842 Issue.update_all("assigned_to_id = NULL")
798 843
799 844 q = Query.new(:name => '_', :group_by => 'assigned_to')
800 845 count_by_group = q.issue_count_by_group
801 846 assert_kind_of Hash, count_by_group
802 847 assert_equal 1, count_by_group.keys.size
803 848 assert_nil count_by_group.keys.first
804 849 end
805 850
806 851 def test_issue_ids
807 852 q = Query.new(:name => '_')
808 853 order = "issues.subject, issues.id"
809 854 issues = q.issues(:order => order)
810 855 assert_equal issues.map(&:id), q.issue_ids(:order => order)
811 856 end
812 857
813 858 def test_label_for
814 859 q = Query.new
815 860 assert_equal 'Assignee', q.label_for('assigned_to_id')
816 861 end
817 862
818 863 def test_editable_by
819 864 admin = User.find(1)
820 865 manager = User.find(2)
821 866 developer = User.find(3)
822 867
823 868 # Public query on project 1
824 869 q = Query.find(1)
825 870 assert q.editable_by?(admin)
826 871 assert q.editable_by?(manager)
827 872 assert !q.editable_by?(developer)
828 873
829 874 # Private query on project 1
830 875 q = Query.find(2)
831 876 assert q.editable_by?(admin)
832 877 assert !q.editable_by?(manager)
833 878 assert q.editable_by?(developer)
834 879
835 880 # Private query for all projects
836 881 q = Query.find(3)
837 882 assert q.editable_by?(admin)
838 883 assert !q.editable_by?(manager)
839 884 assert q.editable_by?(developer)
840 885
841 886 # Public query for all projects
842 887 q = Query.find(4)
843 888 assert q.editable_by?(admin)
844 889 assert !q.editable_by?(manager)
845 890 assert !q.editable_by?(developer)
846 891 end
847 892
848 893 def test_visible_scope
849 894 query_ids = Query.visible(User.anonymous).map(&:id)
850 895
851 896 assert query_ids.include?(1), 'public query on public project was not visible'
852 897 assert query_ids.include?(4), 'public query for all projects was not visible'
853 898 assert !query_ids.include?(2), 'private query on public project was visible'
854 899 assert !query_ids.include?(3), 'private query for all projects was visible'
855 900 assert !query_ids.include?(7), 'public query on private project was visible'
856 901 end
857 902
858 903 context "#available_filters" do
859 904 setup do
860 905 @query = Query.new(:name => "_")
861 906 end
862 907
863 908 should "include users of visible projects in cross-project view" do
864 909 users = @query.available_filters["assigned_to_id"]
865 910 assert_not_nil users
866 911 assert users[:values].map{|u|u[1]}.include?("3")
867 912 end
868 913
869 914 should "include users of subprojects" do
870 915 user1 = User.generate!
871 916 user2 = User.generate!
872 917 project = Project.find(1)
873 918 Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
874 919 @query.project = project
875 920
876 921 users = @query.available_filters["assigned_to_id"]
877 922 assert_not_nil users
878 923 assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
879 924 assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
880 925 end
881 926
882 927 should "include visible projects in cross-project view" do
883 928 projects = @query.available_filters["project_id"]
884 929 assert_not_nil projects
885 930 assert projects[:values].map{|u|u[1]}.include?("1")
886 931 end
887 932
888 933 context "'member_of_group' filter" do
889 934 should "be present" do
890 935 assert @query.available_filters.keys.include?("member_of_group")
891 936 end
892 937
893 938 should "be an optional list" do
894 939 assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
895 940 end
896 941
897 942 should "have a list of the groups as values" do
898 943 Group.destroy_all # No fixtures
899 944 group1 = Group.generate!.reload
900 945 group2 = Group.generate!.reload
901 946
902 947 expected_group_list = [
903 948 [group1.name, group1.id.to_s],
904 949 [group2.name, group2.id.to_s]
905 950 ]
906 951 assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort
907 952 end
908 953
909 954 end
910 955
911 956 context "'assigned_to_role' filter" do
912 957 should "be present" do
913 958 assert @query.available_filters.keys.include?("assigned_to_role")
914 959 end
915 960
916 961 should "be an optional list" do
917 962 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
918 963 end
919 964
920 965 should "have a list of the Roles as values" do
921 966 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
922 967 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
923 968 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
924 969 end
925 970
926 971 should "not include the built in Roles as values" do
927 972 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
928 973 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
929 974 end
930 975
931 976 end
932 977
933 978 end
934 979
935 980 context "#statement" do
936 981 context "with 'member_of_group' filter" do
937 982 setup do
938 983 Group.destroy_all # No fixtures
939 984 @user_in_group = User.generate!
940 985 @second_user_in_group = User.generate!
941 986 @user_in_group2 = User.generate!
942 987 @user_not_in_group = User.generate!
943 988
944 989 @group = Group.generate!.reload
945 990 @group.users << @user_in_group
946 991 @group.users << @second_user_in_group
947 992
948 993 @group2 = Group.generate!.reload
949 994 @group2.users << @user_in_group2
950 995
951 996 end
952 997
953 998 should "search assigned to for users in the group" do
954 999 @query = Query.new(:name => '_')
955 1000 @query.add_filter('member_of_group', '=', [@group.id.to_s])
956 1001
957 1002 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
958 1003 assert_find_issues_with_query_is_successful @query
959 1004 end
960 1005
961 1006 should "search not assigned to any group member (none)" do
962 1007 @query = Query.new(:name => '_')
963 1008 @query.add_filter('member_of_group', '!*', [''])
964 1009
965 1010 # Users not in a group
966 1011 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}')"
967 1012 assert_find_issues_with_query_is_successful @query
968 1013 end
969 1014
970 1015 should "search assigned to any group member (all)" do
971 1016 @query = Query.new(:name => '_')
972 1017 @query.add_filter('member_of_group', '*', [''])
973 1018
974 1019 # Only users in a group
975 1020 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}')"
976 1021 assert_find_issues_with_query_is_successful @query
977 1022 end
978 1023
979 1024 should "return an empty set with = empty group" do
980 1025 @empty_group = Group.generate!
981 1026 @query = Query.new(:name => '_')
982 1027 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
983 1028
984 1029 assert_equal [], find_issues_with_query(@query)
985 1030 end
986 1031
987 1032 should "return issues with ! empty group" do
988 1033 @empty_group = Group.generate!
989 1034 @query = Query.new(:name => '_')
990 1035 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
991 1036
992 1037 assert_find_issues_with_query_is_successful @query
993 1038 end
994 1039 end
995 1040
996 1041 context "with 'assigned_to_role' filter" do
997 1042 setup do
998 1043 @manager_role = Role.find_by_name('Manager')
999 1044 @developer_role = Role.find_by_name('Developer')
1000 1045
1001 1046 @project = Project.generate!
1002 1047 @manager = User.generate!
1003 1048 @developer = User.generate!
1004 1049 @boss = User.generate!
1005 1050 @guest = User.generate!
1006 1051 User.add_to_project(@manager, @project, @manager_role)
1007 1052 User.add_to_project(@developer, @project, @developer_role)
1008 1053 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
1009 1054
1010 1055 @issue1 = Issue.generate_for_project!(@project, :assigned_to_id => @manager.id)
1011 1056 @issue2 = Issue.generate_for_project!(@project, :assigned_to_id => @developer.id)
1012 1057 @issue3 = Issue.generate_for_project!(@project, :assigned_to_id => @boss.id)
1013 1058 @issue4 = Issue.generate_for_project!(@project, :assigned_to_id => @guest.id)
1014 1059 @issue5 = Issue.generate_for_project!(@project)
1015 1060 end
1016 1061
1017 1062 should "search assigned to for users with the Role" do
1018 1063 @query = Query.new(:name => '_', :project => @project)
1019 1064 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1020 1065
1021 1066 assert_query_result [@issue1, @issue3], @query
1022 1067 end
1023 1068
1024 1069 should "search assigned to for users with the Role on the issue project" do
1025 1070 other_project = Project.generate!
1026 1071 User.add_to_project(@developer, other_project, @manager_role)
1027 1072
1028 1073 @query = Query.new(:name => '_', :project => @project)
1029 1074 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1030 1075
1031 1076 assert_query_result [@issue1, @issue3], @query
1032 1077 end
1033 1078
1034 1079 should "return an empty set with empty role" do
1035 1080 @empty_role = Role.generate!
1036 1081 @query = Query.new(:name => '_', :project => @project)
1037 1082 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
1038 1083
1039 1084 assert_query_result [], @query
1040 1085 end
1041 1086
1042 1087 should "search assigned to for users without the Role" do
1043 1088 @query = Query.new(:name => '_', :project => @project)
1044 1089 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
1045 1090
1046 1091 assert_query_result [@issue2, @issue4, @issue5], @query
1047 1092 end
1048 1093
1049 1094 should "search assigned to for users not assigned to any Role (none)" do
1050 1095 @query = Query.new(:name => '_', :project => @project)
1051 1096 @query.add_filter('assigned_to_role', '!*', [''])
1052 1097
1053 1098 assert_query_result [@issue4, @issue5], @query
1054 1099 end
1055 1100
1056 1101 should "search assigned to for users assigned to any Role (all)" do
1057 1102 @query = Query.new(:name => '_', :project => @project)
1058 1103 @query.add_filter('assigned_to_role', '*', [''])
1059 1104
1060 1105 assert_query_result [@issue1, @issue2, @issue3], @query
1061 1106 end
1062 1107
1063 1108 should "return issues with ! empty role" do
1064 1109 @empty_role = Role.generate!
1065 1110 @query = Query.new(:name => '_', :project => @project)
1066 1111 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
1067 1112
1068 1113 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
1069 1114 end
1070 1115 end
1071 1116 end
1072 1117
1073 1118 end
General Comments 0
You need to be logged in to leave comments. Login now