##// 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 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueryColumn
18 class QueryColumn
19 attr_accessor :name, :sortable, :groupable, :default_order
19 attr_accessor :name, :sortable, :groupable, :default_order
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 def initialize(name, options={})
22 def initialize(name, options={})
23 self.name = name
23 self.name = name
24 self.sortable = options[:sortable]
24 self.sortable = options[:sortable]
25 self.groupable = options[:groupable] || false
25 self.groupable = options[:groupable] || false
26 if groupable == true
26 if groupable == true
27 self.groupable = name.to_s
27 self.groupable = name.to_s
28 end
28 end
29 self.default_order = options[:default_order]
29 self.default_order = options[:default_order]
30 @caption_key = options[:caption] || "field_#{name}"
30 @caption_key = options[:caption] || "field_#{name}"
31 end
31 end
32
32
33 def caption
33 def caption
34 l(@caption_key)
34 l(@caption_key)
35 end
35 end
36
36
37 # Returns true if the column is sortable, otherwise false
37 # Returns true if the column is sortable, otherwise false
38 def sortable?
38 def sortable?
39 !@sortable.nil?
39 !@sortable.nil?
40 end
40 end
41
41
42 def sortable
42 def sortable
43 @sortable.is_a?(Proc) ? @sortable.call : @sortable
43 @sortable.is_a?(Proc) ? @sortable.call : @sortable
44 end
44 end
45
45
46 def value(issue)
46 def value(issue)
47 issue.send name
47 issue.send name
48 end
48 end
49
49
50 def css_classes
50 def css_classes
51 name
51 name
52 end
52 end
53 end
53 end
54
54
55 class QueryCustomFieldColumn < QueryColumn
55 class QueryCustomFieldColumn < QueryColumn
56
56
57 def initialize(custom_field)
57 def initialize(custom_field)
58 self.name = "cf_#{custom_field.id}".to_sym
58 self.name = "cf_#{custom_field.id}".to_sym
59 self.sortable = custom_field.order_statement || false
59 self.sortable = custom_field.order_statement || false
60 self.groupable = custom_field.group_statement || false
60 self.groupable = custom_field.group_statement || false
61 @cf = custom_field
61 @cf = custom_field
62 end
62 end
63
63
64 def caption
64 def caption
65 @cf.name
65 @cf.name
66 end
66 end
67
67
68 def custom_field
68 def custom_field
69 @cf
69 @cf
70 end
70 end
71
71
72 def value(issue)
72 def value(issue)
73 cv = issue.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
73 cv = issue.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
74 cv.size > 1 ? cv : cv.first
74 cv.size > 1 ? cv : cv.first
75 end
75 end
76
76
77 def css_classes
77 def css_classes
78 @css_classes ||= "#{name} #{@cf.field_format}"
78 @css_classes ||= "#{name} #{@cf.field_format}"
79 end
79 end
80 end
80 end
81
81
82 class Query < ActiveRecord::Base
82 class Query < ActiveRecord::Base
83 class StatementInvalid < ::ActiveRecord::StatementInvalid
83 class StatementInvalid < ::ActiveRecord::StatementInvalid
84 end
84 end
85
85
86 belongs_to :project
86 belongs_to :project
87 belongs_to :user
87 belongs_to :user
88 serialize :filters
88 serialize :filters
89 serialize :column_names
89 serialize :column_names
90 serialize :sort_criteria, Array
90 serialize :sort_criteria, Array
91
91
92 attr_protected :project_id, :user_id
92 attr_protected :project_id, :user_id
93
93
94 validates_presence_of :name
94 validates_presence_of :name
95 validates_length_of :name, :maximum => 255
95 validates_length_of :name, :maximum => 255
96 validate :validate_query_filters
96 validate :validate_query_filters
97
97
98 @@operators = { "=" => :label_equals,
98 @@operators = { "=" => :label_equals,
99 "!" => :label_not_equals,
99 "!" => :label_not_equals,
100 "o" => :label_open_issues,
100 "o" => :label_open_issues,
101 "c" => :label_closed_issues,
101 "c" => :label_closed_issues,
102 "!*" => :label_none,
102 "!*" => :label_none,
103 "*" => :label_all,
103 "*" => :label_all,
104 ">=" => :label_greater_or_equal,
104 ">=" => :label_greater_or_equal,
105 "<=" => :label_less_or_equal,
105 "<=" => :label_less_or_equal,
106 "><" => :label_between,
106 "><" => :label_between,
107 "<t+" => :label_in_less_than,
107 "<t+" => :label_in_less_than,
108 ">t+" => :label_in_more_than,
108 ">t+" => :label_in_more_than,
109 "t+" => :label_in,
109 "t+" => :label_in,
110 "t" => :label_today,
110 "t" => :label_today,
111 "w" => :label_this_week,
111 "w" => :label_this_week,
112 ">t-" => :label_less_than_ago,
112 ">t-" => :label_less_than_ago,
113 "<t-" => :label_more_than_ago,
113 "<t-" => :label_more_than_ago,
114 "t-" => :label_ago,
114 "t-" => :label_ago,
115 "~" => :label_contains,
115 "~" => :label_contains,
116 "!~" => :label_not_contains }
116 "!~" => :label_not_contains }
117
117
118 cattr_reader :operators
118 cattr_reader :operators
119
119
120 @@operators_by_filter_type = { :list => [ "=", "!" ],
120 @@operators_by_filter_type = { :list => [ "=", "!" ],
121 :list_status => [ "o", "=", "!", "c", "*" ],
121 :list_status => [ "o", "=", "!", "c", "*" ],
122 :list_optional => [ "=", "!", "!*", "*" ],
122 :list_optional => [ "=", "!", "!*", "*" ],
123 :list_subprojects => [ "*", "!*", "=" ],
123 :list_subprojects => [ "*", "!*", "=" ],
124 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-", "!*", "*" ],
124 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-", "!*", "*" ],
125 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w", "!*", "*" ],
125 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w", "!*", "*" ],
126 :string => [ "=", "~", "!", "!~", "!*", "*" ],
126 :string => [ "=", "~", "!", "!~", "!*", "*" ],
127 :text => [ "~", "!~", "!*", "*" ],
127 :text => [ "~", "!~", "!*", "*" ],
128 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
128 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
129 :float => [ "=", ">=", "<=", "><", "!*", "*" ] }
129 :float => [ "=", ">=", "<=", "><", "!*", "*" ] }
130
130
131 cattr_reader :operators_by_filter_type
131 cattr_reader :operators_by_filter_type
132
132
133 @@available_columns = [
133 @@available_columns = [
134 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
134 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
135 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
135 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
136 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
136 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
137 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
137 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
138 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
138 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
139 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
139 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
140 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
140 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
141 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
141 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
142 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
142 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
143 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
143 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
144 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
144 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
145 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
145 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
146 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
146 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
147 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
147 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
148 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
148 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
149 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
149 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
150 ]
150 ]
151 cattr_reader :available_columns
151 cattr_reader :available_columns
152
152
153 scope :visible, lambda {|*args|
153 scope :visible, lambda {|*args|
154 user = args.shift || User.current
154 user = args.shift || User.current
155 base = Project.allowed_to_condition(user, :view_issues, *args)
155 base = Project.allowed_to_condition(user, :view_issues, *args)
156 user_id = user.logged? ? user.id : 0
156 user_id = user.logged? ? user.id : 0
157 {
157 {
158 :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id],
158 :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id],
159 :include => :project
159 :include => :project
160 }
160 }
161 }
161 }
162
162
163 def initialize(attributes=nil, *args)
163 def initialize(attributes=nil, *args)
164 super attributes
164 super attributes
165 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
165 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
166 @is_for_all = project.nil?
166 @is_for_all = project.nil?
167 end
167 end
168
168
169 def validate_query_filters
169 def validate_query_filters
170 filters.each_key do |field|
170 filters.each_key do |field|
171 if values_for(field)
171 if values_for(field)
172 case type_for(field)
172 case type_for(field)
173 when :integer
173 when :integer
174 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
174 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
175 when :float
175 when :float
176 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
176 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
177 when :date, :date_past
177 when :date, :date_past
178 case operator_for(field)
178 case operator_for(field)
179 when "=", ">=", "<=", "><"
179 when "=", ">=", "<=", "><"
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?) }
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 when ">t-", "<t-", "t-"
181 when ">t-", "<t-", "t-"
182 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
182 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
183 end
183 end
184 end
184 end
185 end
185 end
186
186
187 add_filter_error(field, :blank) unless
187 add_filter_error(field, :blank) unless
188 # filter requires one or more values
188 # filter requires one or more values
189 (values_for(field) and !values_for(field).first.blank?) or
189 (values_for(field) and !values_for(field).first.blank?) or
190 # filter doesn't require any value
190 # filter doesn't require any value
191 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
191 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
192 end if filters
192 end if filters
193 end
193 end
194
194
195 def add_filter_error(field, message)
195 def add_filter_error(field, message)
196 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
196 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
197 errors.add(:base, m)
197 errors.add(:base, m)
198 end
198 end
199
199
200 # Returns true if the query is visible to +user+ or the current user.
200 # Returns true if the query is visible to +user+ or the current user.
201 def visible?(user=User.current)
201 def visible?(user=User.current)
202 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
202 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
203 end
203 end
204
204
205 def editable_by?(user)
205 def editable_by?(user)
206 return false unless user
206 return false unless user
207 # Admin can edit them all and regular users can edit their private queries
207 # Admin can edit them all and regular users can edit their private queries
208 return true if user.admin? || (!is_public && self.user_id == user.id)
208 return true if user.admin? || (!is_public && self.user_id == user.id)
209 # Members can not edit public queries that are for all project (only admin is allowed to)
209 # Members can not edit public queries that are for all project (only admin is allowed to)
210 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
210 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
211 end
211 end
212
212
213 def trackers
213 def trackers
214 @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
214 @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
215 end
215 end
216
216
217 # Returns a hash of localized labels for all filter operators
217 # Returns a hash of localized labels for all filter operators
218 def self.operators_labels
218 def self.operators_labels
219 operators.inject({}) {|h, operator| h[operator.first] = l(operator.last); h}
219 operators.inject({}) {|h, operator| h[operator.first] = l(operator.last); h}
220 end
220 end
221
221
222 def available_filters
222 def available_filters
223 return @available_filters if @available_filters
223 return @available_filters if @available_filters
224
224
225 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
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 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
226 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
227 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
227 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
228 "subject" => { :type => :text, :order => 8 },
228 "subject" => { :type => :text, :order => 8 },
229 "created_on" => { :type => :date_past, :order => 9 },
229 "created_on" => { :type => :date_past, :order => 9 },
230 "updated_on" => { :type => :date_past, :order => 10 },
230 "updated_on" => { :type => :date_past, :order => 10 },
231 "start_date" => { :type => :date, :order => 11 },
231 "start_date" => { :type => :date, :order => 11 },
232 "due_date" => { :type => :date, :order => 12 },
232 "due_date" => { :type => :date, :order => 12 },
233 "estimated_hours" => { :type => :float, :order => 13 },
233 "estimated_hours" => { :type => :float, :order => 13 },
234 "done_ratio" => { :type => :integer, :order => 14 }}
234 "done_ratio" => { :type => :integer, :order => 14 }}
235
235
236 principals = []
236 principals = []
237 if project
237 if project
238 principals += project.principals.sort
238 principals += project.principals.sort
239 unless project.leaf?
239 unless project.leaf?
240 subprojects = project.descendants.visible.all
240 subprojects = project.descendants.visible.all
241 if subprojects.any?
241 if subprojects.any?
242 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
242 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
243 principals += Principal.member_of(subprojects)
243 principals += Principal.member_of(subprojects)
244 end
244 end
245 end
245 end
246 else
246 else
247 all_projects = Project.visible.all
247 all_projects = Project.visible.all
248 if all_projects.any?
248 if all_projects.any?
249 # members of visible projects
249 # members of visible projects
250 principals += Principal.member_of(all_projects)
250 principals += Principal.member_of(all_projects)
251
251
252 # project filter
252 # project filter
253 project_values = []
253 project_values = []
254 if User.current.logged? && User.current.memberships.any?
254 if User.current.logged? && User.current.memberships.any?
255 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
255 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
256 end
256 end
257 Project.project_tree(all_projects) do |p, level|
257 Project.project_tree(all_projects) do |p, level|
258 prefix = (level > 0 ? ('--' * level + ' ') : '')
258 prefix = (level > 0 ? ('--' * level + ' ') : '')
259 project_values << ["#{prefix}#{p.name}", p.id.to_s]
259 project_values << ["#{prefix}#{p.name}", p.id.to_s]
260 end
260 end
261 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
261 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
262 end
262 end
263 end
263 end
264 principals.uniq!
264 principals.uniq!
265 principals.sort!
265 principals.sort!
266 users = principals.select {|p| p.is_a?(User)}
266 users = principals.select {|p| p.is_a?(User)}
267
267
268 assigned_to_values = []
268 assigned_to_values = []
269 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
269 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
270 assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
270 assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
271 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
271 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
272
272
273 author_values = []
273 author_values = []
274 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
274 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
275 author_values += users.collect{|s| [s.name, s.id.to_s] }
275 author_values += users.collect{|s| [s.name, s.id.to_s] }
276 @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
276 @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
277
277
278 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
278 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
279 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
279 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
280
280
281 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
281 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
282 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
282 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
283
283
284 if User.current.logged?
284 if User.current.logged?
285 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
285 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
286 end
286 end
287
287
288 if project
288 if project
289 # project specific filters
289 # project specific filters
290 categories = project.issue_categories.all
290 categories = project.issue_categories.all
291 unless categories.empty?
291 unless categories.empty?
292 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
292 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
293 end
293 end
294 versions = project.shared_versions.all
294 versions = project.shared_versions.all
295 unless versions.empty?
295 unless versions.empty?
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] } }
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 end
297 end
298 add_custom_fields_filters(project.all_issue_custom_fields)
298 add_custom_fields_filters(project.all_issue_custom_fields)
299 else
299 else
300 # global filters for cross project issue list
300 # global filters for cross project issue list
301 system_shared_versions = Version.visible.find_all_by_sharing('system')
301 system_shared_versions = Version.visible.find_all_by_sharing('system')
302 unless system_shared_versions.empty?
302 unless system_shared_versions.empty?
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] } }
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 end
304 end
305 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
305 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
306 end
306 end
307
307
308 add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
309
308 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
310 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
309 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
311 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
310 @available_filters["is_private"] = { :type => :list, :order => 15, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] }
312 @available_filters["is_private"] = { :type => :list, :order => 15, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] }
311 end
313 end
312
314
313 Tracker.disabled_core_fields(trackers).each {|field|
315 Tracker.disabled_core_fields(trackers).each {|field|
314 @available_filters.delete field
316 @available_filters.delete field
315 }
317 }
316
318
317 @available_filters.each do |field, options|
319 @available_filters.each do |field, options|
318 options[:name] ||= l("field_#{field}".gsub(/_id$/, ''))
320 options[:name] ||= l("field_#{field}".gsub(/_id$/, ''))
319 end
321 end
320
322
321 @available_filters
323 @available_filters
322 end
324 end
323
325
324 # Returns a representation of the available filters for JSON serialization
326 # Returns a representation of the available filters for JSON serialization
325 def available_filters_as_json
327 def available_filters_as_json
326 json = {}
328 json = {}
327 available_filters.each do |field, options|
329 available_filters.each do |field, options|
328 json[field] = options.slice(:type, :name, :values).stringify_keys
330 json[field] = options.slice(:type, :name, :values).stringify_keys
329 end
331 end
330 json
332 json
331 end
333 end
332
334
333 def add_filter(field, operator, values)
335 def add_filter(field, operator, values)
334 # values must be an array
336 # values must be an array
335 return unless values.nil? || values.is_a?(Array)
337 return unless values.nil? || values.is_a?(Array)
336 # check if field is defined as an available filter
338 # check if field is defined as an available filter
337 if available_filters.has_key? field
339 if available_filters.has_key? field
338 filter_options = available_filters[field]
340 filter_options = available_filters[field]
339 # check if operator is allowed for that filter
341 # check if operator is allowed for that filter
340 #if @@operators_by_filter_type[filter_options[:type]].include? operator
342 #if @@operators_by_filter_type[filter_options[:type]].include? operator
341 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
343 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
342 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
344 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
343 #end
345 #end
344 filters[field] = {:operator => operator, :values => (values || [''])}
346 filters[field] = {:operator => operator, :values => (values || [''])}
345 end
347 end
346 end
348 end
347
349
348 def add_short_filter(field, expression)
350 def add_short_filter(field, expression)
349 return unless expression && available_filters.has_key?(field)
351 return unless expression && available_filters.has_key?(field)
350 field_type = available_filters[field][:type]
352 field_type = available_filters[field][:type]
351 @@operators_by_filter_type[field_type].sort.reverse.detect do |operator|
353 @@operators_by_filter_type[field_type].sort.reverse.detect do |operator|
352 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
354 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
353 add_filter field, operator, $1.present? ? $1.split('|') : ['']
355 add_filter field, operator, $1.present? ? $1.split('|') : ['']
354 end || add_filter(field, '=', expression.split('|'))
356 end || add_filter(field, '=', expression.split('|'))
355 end
357 end
356
358
357 # Add multiple filters using +add_filter+
359 # Add multiple filters using +add_filter+
358 def add_filters(fields, operators, values)
360 def add_filters(fields, operators, values)
359 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
361 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
360 fields.each do |field|
362 fields.each do |field|
361 add_filter(field, operators[field], values && values[field])
363 add_filter(field, operators[field], values && values[field])
362 end
364 end
363 end
365 end
364 end
366 end
365
367
366 def has_filter?(field)
368 def has_filter?(field)
367 filters and filters[field]
369 filters and filters[field]
368 end
370 end
369
371
370 def type_for(field)
372 def type_for(field)
371 available_filters[field][:type] if available_filters.has_key?(field)
373 available_filters[field][:type] if available_filters.has_key?(field)
372 end
374 end
373
375
374 def operator_for(field)
376 def operator_for(field)
375 has_filter?(field) ? filters[field][:operator] : nil
377 has_filter?(field) ? filters[field][:operator] : nil
376 end
378 end
377
379
378 def values_for(field)
380 def values_for(field)
379 has_filter?(field) ? filters[field][:values] : nil
381 has_filter?(field) ? filters[field][:values] : nil
380 end
382 end
381
383
382 def value_for(field, index=0)
384 def value_for(field, index=0)
383 (values_for(field) || [])[index]
385 (values_for(field) || [])[index]
384 end
386 end
385
387
386 def label_for(field)
388 def label_for(field)
387 label = available_filters[field][:name] if available_filters.has_key?(field)
389 label = available_filters[field][:name] if available_filters.has_key?(field)
388 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
390 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
389 end
391 end
390
392
391 def available_columns
393 def available_columns
392 return @available_columns if @available_columns
394 return @available_columns if @available_columns
393 @available_columns = ::Query.available_columns.dup
395 @available_columns = ::Query.available_columns.dup
394 @available_columns += (project ?
396 @available_columns += (project ?
395 project.all_issue_custom_fields :
397 project.all_issue_custom_fields :
396 IssueCustomField.find(:all)
398 IssueCustomField.find(:all)
397 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
399 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
398
400
399 if User.current.allowed_to?(:view_time_entries, project, :global => true)
401 if User.current.allowed_to?(:view_time_entries, project, :global => true)
400 index = nil
402 index = nil
401 @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours}
403 @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours}
402 index = (index ? index + 1 : -1)
404 index = (index ? index + 1 : -1)
403 # insert the column after estimated_hours or at the end
405 # insert the column after estimated_hours or at the end
404 @available_columns.insert index, QueryColumn.new(:spent_hours,
406 @available_columns.insert index, QueryColumn.new(:spent_hours,
405 :sortable => "(SELECT COALESCE(SUM(hours), 0) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id)",
407 :sortable => "(SELECT COALESCE(SUM(hours), 0) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id)",
406 :default_order => 'desc',
408 :default_order => 'desc',
407 :caption => :label_spent_time
409 :caption => :label_spent_time
408 )
410 )
409 end
411 end
410
412
411 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
413 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
412 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
414 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
413 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
415 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
414 end
416 end
415
417
416 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
418 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
417 @available_columns.reject! {|column|
419 @available_columns.reject! {|column|
418 disabled_fields.include?(column.name.to_s)
420 disabled_fields.include?(column.name.to_s)
419 }
421 }
420
422
421 @available_columns
423 @available_columns
422 end
424 end
423
425
424 def self.available_columns=(v)
426 def self.available_columns=(v)
425 self.available_columns = (v)
427 self.available_columns = (v)
426 end
428 end
427
429
428 def self.add_available_column(column)
430 def self.add_available_column(column)
429 self.available_columns << (column) if column.is_a?(QueryColumn)
431 self.available_columns << (column) if column.is_a?(QueryColumn)
430 end
432 end
431
433
432 # Returns an array of columns that can be used to group the results
434 # Returns an array of columns that can be used to group the results
433 def groupable_columns
435 def groupable_columns
434 available_columns.select {|c| c.groupable}
436 available_columns.select {|c| c.groupable}
435 end
437 end
436
438
437 # Returns a Hash of columns and the key for sorting
439 # Returns a Hash of columns and the key for sorting
438 def sortable_columns
440 def sortable_columns
439 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
441 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
440 h[column.name.to_s] = column.sortable
442 h[column.name.to_s] = column.sortable
441 h
443 h
442 })
444 })
443 end
445 end
444
446
445 def columns
447 def columns
446 # preserve the column_names order
448 # preserve the column_names order
447 (has_default_columns? ? default_columns_names : column_names).collect do |name|
449 (has_default_columns? ? default_columns_names : column_names).collect do |name|
448 available_columns.find { |col| col.name == name }
450 available_columns.find { |col| col.name == name }
449 end.compact
451 end.compact
450 end
452 end
451
453
452 def default_columns_names
454 def default_columns_names
453 @default_columns_names ||= begin
455 @default_columns_names ||= begin
454 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
456 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
455
457
456 project.present? ? default_columns : [:project] | default_columns
458 project.present? ? default_columns : [:project] | default_columns
457 end
459 end
458 end
460 end
459
461
460 def column_names=(names)
462 def column_names=(names)
461 if names
463 if names
462 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
464 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
463 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
465 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
464 # Set column_names to nil if default columns
466 # Set column_names to nil if default columns
465 if names == default_columns_names
467 if names == default_columns_names
466 names = nil
468 names = nil
467 end
469 end
468 end
470 end
469 write_attribute(:column_names, names)
471 write_attribute(:column_names, names)
470 end
472 end
471
473
472 def has_column?(column)
474 def has_column?(column)
473 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
475 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
474 end
476 end
475
477
476 def has_default_columns?
478 def has_default_columns?
477 column_names.nil? || column_names.empty?
479 column_names.nil? || column_names.empty?
478 end
480 end
479
481
480 def sort_criteria=(arg)
482 def sort_criteria=(arg)
481 c = []
483 c = []
482 if arg.is_a?(Hash)
484 if arg.is_a?(Hash)
483 arg = arg.keys.sort.collect {|k| arg[k]}
485 arg = arg.keys.sort.collect {|k| arg[k]}
484 end
486 end
485 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
487 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
486 write_attribute(:sort_criteria, c)
488 write_attribute(:sort_criteria, c)
487 end
489 end
488
490
489 def sort_criteria
491 def sort_criteria
490 read_attribute(:sort_criteria) || []
492 read_attribute(:sort_criteria) || []
491 end
493 end
492
494
493 def sort_criteria_key(arg)
495 def sort_criteria_key(arg)
494 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
496 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
495 end
497 end
496
498
497 def sort_criteria_order(arg)
499 def sort_criteria_order(arg)
498 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
500 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
499 end
501 end
500
502
501 # Returns the SQL sort order that should be prepended for grouping
503 # Returns the SQL sort order that should be prepended for grouping
502 def group_by_sort_order
504 def group_by_sort_order
503 if grouped? && (column = group_by_column)
505 if grouped? && (column = group_by_column)
504 column.sortable.is_a?(Array) ?
506 column.sortable.is_a?(Array) ?
505 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
507 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
506 "#{column.sortable} #{column.default_order}"
508 "#{column.sortable} #{column.default_order}"
507 end
509 end
508 end
510 end
509
511
510 # Returns true if the query is a grouped query
512 # Returns true if the query is a grouped query
511 def grouped?
513 def grouped?
512 !group_by_column.nil?
514 !group_by_column.nil?
513 end
515 end
514
516
515 def group_by_column
517 def group_by_column
516 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
518 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
517 end
519 end
518
520
519 def group_by_statement
521 def group_by_statement
520 group_by_column.try(:groupable)
522 group_by_column.try(:groupable)
521 end
523 end
522
524
523 def project_statement
525 def project_statement
524 project_clauses = []
526 project_clauses = []
525 if project && !project.descendants.active.empty?
527 if project && !project.descendants.active.empty?
526 ids = [project.id]
528 ids = [project.id]
527 if has_filter?("subproject_id")
529 if has_filter?("subproject_id")
528 case operator_for("subproject_id")
530 case operator_for("subproject_id")
529 when '='
531 when '='
530 # include the selected subprojects
532 # include the selected subprojects
531 ids += values_for("subproject_id").each(&:to_i)
533 ids += values_for("subproject_id").each(&:to_i)
532 when '!*'
534 when '!*'
533 # main project only
535 # main project only
534 else
536 else
535 # all subprojects
537 # all subprojects
536 ids += project.descendants.collect(&:id)
538 ids += project.descendants.collect(&:id)
537 end
539 end
538 elsif Setting.display_subprojects_issues?
540 elsif Setting.display_subprojects_issues?
539 ids += project.descendants.collect(&:id)
541 ids += project.descendants.collect(&:id)
540 end
542 end
541 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
543 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
542 elsif project
544 elsif project
543 project_clauses << "#{Project.table_name}.id = %d" % project.id
545 project_clauses << "#{Project.table_name}.id = %d" % project.id
544 end
546 end
545 project_clauses.any? ? project_clauses.join(' AND ') : nil
547 project_clauses.any? ? project_clauses.join(' AND ') : nil
546 end
548 end
547
549
548 def statement
550 def statement
549 # filters clauses
551 # filters clauses
550 filters_clauses = []
552 filters_clauses = []
551 filters.each_key do |field|
553 filters.each_key do |field|
552 next if field == "subproject_id"
554 next if field == "subproject_id"
553 v = values_for(field).clone
555 v = values_for(field).clone
554 next unless v and !v.empty?
556 next unless v and !v.empty?
555 operator = operator_for(field)
557 operator = operator_for(field)
556
558
557 # "me" value subsitution
559 # "me" value subsitution
558 if %w(assigned_to_id author_id watcher_id).include?(field)
560 if %w(assigned_to_id author_id watcher_id).include?(field)
559 if v.delete("me")
561 if v.delete("me")
560 if User.current.logged?
562 if User.current.logged?
561 v.push(User.current.id.to_s)
563 v.push(User.current.id.to_s)
562 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
564 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
563 else
565 else
564 v.push("0")
566 v.push("0")
565 end
567 end
566 end
568 end
567 end
569 end
568
570
569 if field == 'project_id'
571 if field == 'project_id'
570 if v.delete('mine')
572 if v.delete('mine')
571 v += User.current.memberships.map(&:project_id).map(&:to_s)
573 v += User.current.memberships.map(&:project_id).map(&:to_s)
572 end
574 end
573 end
575 end
574
576
575 if field =~ /^cf_(\d+)$/
577 if field =~ /cf_(\d+)$/
576 # custom field
578 # custom field
577 filters_clauses << sql_for_custom_field(field, operator, v, $1)
579 filters_clauses << sql_for_custom_field(field, operator, v, $1)
578 elsif respond_to?("sql_for_#{field}_field")
580 elsif respond_to?("sql_for_#{field}_field")
579 # specific statement
581 # specific statement
580 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
582 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
581 else
583 else
582 # regular field
584 # regular field
583 filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')'
585 filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')'
584 end
586 end
585 end if filters and valid?
587 end if filters and valid?
586
588
587 filters_clauses << project_statement
589 filters_clauses << project_statement
588 filters_clauses.reject!(&:blank?)
590 filters_clauses.reject!(&:blank?)
589
591
590 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
592 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
591 end
593 end
592
594
593 # Returns the issue count
595 # Returns the issue count
594 def issue_count
596 def issue_count
595 Issue.visible.count(:include => [:status, :project], :conditions => statement)
597 Issue.visible.count(:include => [:status, :project], :conditions => statement)
596 rescue ::ActiveRecord::StatementInvalid => e
598 rescue ::ActiveRecord::StatementInvalid => e
597 raise StatementInvalid.new(e.message)
599 raise StatementInvalid.new(e.message)
598 end
600 end
599
601
600 # Returns the issue count by group or nil if query is not grouped
602 # Returns the issue count by group or nil if query is not grouped
601 def issue_count_by_group
603 def issue_count_by_group
602 r = nil
604 r = nil
603 if grouped?
605 if grouped?
604 begin
606 begin
605 # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value
607 # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value
606 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
608 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
607 rescue ActiveRecord::RecordNotFound
609 rescue ActiveRecord::RecordNotFound
608 r = {nil => issue_count}
610 r = {nil => issue_count}
609 end
611 end
610 c = group_by_column
612 c = group_by_column
611 if c.is_a?(QueryCustomFieldColumn)
613 if c.is_a?(QueryCustomFieldColumn)
612 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
614 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
613 end
615 end
614 end
616 end
615 r
617 r
616 rescue ::ActiveRecord::StatementInvalid => e
618 rescue ::ActiveRecord::StatementInvalid => e
617 raise StatementInvalid.new(e.message)
619 raise StatementInvalid.new(e.message)
618 end
620 end
619
621
620 # Returns the issues
622 # Returns the issues
621 # Valid options are :order, :offset, :limit, :include, :conditions
623 # Valid options are :order, :offset, :limit, :include, :conditions
622 def issues(options={})
624 def issues(options={})
623 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
625 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
624 order_option = nil if order_option.blank?
626 order_option = nil if order_option.blank?
625
627
626 issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
628 issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
627 :conditions => statement,
629 :conditions => statement,
628 :order => order_option,
630 :order => order_option,
629 :joins => joins_for_order_statement(order_option),
631 :joins => joins_for_order_statement(order_option),
630 :limit => options[:limit],
632 :limit => options[:limit],
631 :offset => options[:offset]
633 :offset => options[:offset]
632
634
633 if has_column?(:spent_hours)
635 if has_column?(:spent_hours)
634 Issue.load_visible_spent_hours(issues)
636 Issue.load_visible_spent_hours(issues)
635 end
637 end
636 issues
638 issues
637 rescue ::ActiveRecord::StatementInvalid => e
639 rescue ::ActiveRecord::StatementInvalid => e
638 raise StatementInvalid.new(e.message)
640 raise StatementInvalid.new(e.message)
639 end
641 end
640
642
641 # Returns the issues ids
643 # Returns the issues ids
642 def issue_ids(options={})
644 def issue_ids(options={})
643 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
645 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
644 order_option = nil if order_option.blank?
646 order_option = nil if order_option.blank?
645
647
646 Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq,
648 Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq,
647 :conditions => statement,
649 :conditions => statement,
648 :order => order_option,
650 :order => order_option,
649 :joins => joins_for_order_statement(order_option),
651 :joins => joins_for_order_statement(order_option),
650 :limit => options[:limit],
652 :limit => options[:limit],
651 :offset => options[:offset]).find_ids
653 :offset => options[:offset]).find_ids
652 rescue ::ActiveRecord::StatementInvalid => e
654 rescue ::ActiveRecord::StatementInvalid => e
653 raise StatementInvalid.new(e.message)
655 raise StatementInvalid.new(e.message)
654 end
656 end
655
657
656 # Returns the journals
658 # Returns the journals
657 # Valid options are :order, :offset, :limit
659 # Valid options are :order, :offset, :limit
658 def journals(options={})
660 def journals(options={})
659 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
661 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
660 :conditions => statement,
662 :conditions => statement,
661 :order => options[:order],
663 :order => options[:order],
662 :limit => options[:limit],
664 :limit => options[:limit],
663 :offset => options[:offset]
665 :offset => options[:offset]
664 rescue ::ActiveRecord::StatementInvalid => e
666 rescue ::ActiveRecord::StatementInvalid => e
665 raise StatementInvalid.new(e.message)
667 raise StatementInvalid.new(e.message)
666 end
668 end
667
669
668 # Returns the versions
670 # Returns the versions
669 # Valid options are :conditions
671 # Valid options are :conditions
670 def versions(options={})
672 def versions(options={})
671 Version.visible.scoped(:conditions => options[:conditions]).find :all, :include => :project, :conditions => project_statement
673 Version.visible.scoped(:conditions => options[:conditions]).find :all, :include => :project, :conditions => project_statement
672 rescue ::ActiveRecord::StatementInvalid => e
674 rescue ::ActiveRecord::StatementInvalid => e
673 raise StatementInvalid.new(e.message)
675 raise StatementInvalid.new(e.message)
674 end
676 end
675
677
676 def sql_for_watcher_id_field(field, operator, value)
678 def sql_for_watcher_id_field(field, operator, value)
677 db_table = Watcher.table_name
679 db_table = Watcher.table_name
678 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
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 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
681 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
680 end
682 end
681
683
682 def sql_for_member_of_group_field(field, operator, value)
684 def sql_for_member_of_group_field(field, operator, value)
683 if operator == '*' # Any group
685 if operator == '*' # Any group
684 groups = Group.all
686 groups = Group.all
685 operator = '=' # Override the operator since we want to find by assigned_to
687 operator = '=' # Override the operator since we want to find by assigned_to
686 elsif operator == "!*"
688 elsif operator == "!*"
687 groups = Group.all
689 groups = Group.all
688 operator = '!' # Override the operator since we want to find by assigned_to
690 operator = '!' # Override the operator since we want to find by assigned_to
689 else
691 else
690 groups = Group.find_all_by_id(value)
692 groups = Group.find_all_by_id(value)
691 end
693 end
692 groups ||= []
694 groups ||= []
693
695
694 members_of_groups = groups.inject([]) {|user_ids, group|
696 members_of_groups = groups.inject([]) {|user_ids, group|
695 if group && group.user_ids.present?
697 if group && group.user_ids.present?
696 user_ids << group.user_ids
698 user_ids << group.user_ids
697 end
699 end
698 user_ids.flatten.uniq.compact
700 user_ids.flatten.uniq.compact
699 }.sort.collect(&:to_s)
701 }.sort.collect(&:to_s)
700
702
701 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
703 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
702 end
704 end
703
705
704 def sql_for_assigned_to_role_field(field, operator, value)
706 def sql_for_assigned_to_role_field(field, operator, value)
705 case operator
707 case operator
706 when "*", "!*" # Member / Not member
708 when "*", "!*" # Member / Not member
707 sw = operator == "!*" ? 'NOT' : ''
709 sw = operator == "!*" ? 'NOT' : ''
708 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
710 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
709 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
711 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
710 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
712 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
711 when "=", "!"
713 when "=", "!"
712 role_cond = value.any? ?
714 role_cond = value.any? ?
713 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" :
715 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" :
714 "1=0"
716 "1=0"
715
717
716 sw = operator == "!" ? 'NOT' : ''
718 sw = operator == "!" ? 'NOT' : ''
717 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
719 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
718 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
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 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
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 end
722 end
721 end
723 end
722
724
723 def sql_for_is_private_field(field, operator, value)
725 def sql_for_is_private_field(field, operator, value)
724 op = (operator == "=" ? 'IN' : 'NOT IN')
726 op = (operator == "=" ? 'IN' : 'NOT IN')
725 va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',')
727 va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',')
726
728
727 "#{Issue.table_name}.is_private #{op} (#{va})"
729 "#{Issue.table_name}.is_private #{op} (#{va})"
728 end
730 end
729
731
730 private
732 private
731
733
732 def sql_for_custom_field(field, operator, value, custom_field_id)
734 def sql_for_custom_field(field, operator, value, custom_field_id)
733 db_table = CustomValue.table_name
735 db_table = CustomValue.table_name
734 db_field = 'value'
736 db_field = 'value'
735 filter = @available_filters[field]
737 filter = @available_filters[field]
736 if filter && filter[:format] == 'user'
738 return nil unless filter
739 if filter[:format] == 'user'
737 if value.delete('me')
740 if value.delete('me')
738 value.push User.current.id.to_s
741 value.push User.current.id.to_s
739 end
742 end
740 end
743 end
741 not_in = nil
744 not_in = nil
742 if operator == '!'
745 if operator == '!'
743 # Makes ! operator work for custom fields with multiple values
746 # Makes ! operator work for custom fields with multiple values
744 operator = '='
747 operator = '='
745 not_in = 'NOT'
748 not_in = 'NOT'
746 end
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 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
759 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
749 end
760 end
750
761
751 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
762 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
752 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
763 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
753 sql = ''
764 sql = ''
754 case operator
765 case operator
755 when "="
766 when "="
756 if value.any?
767 if value.any?
757 case type_for(field)
768 case type_for(field)
758 when :date, :date_past
769 when :date, :date_past
759 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
770 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
760 when :integer
771 when :integer
761 if is_custom_filter
772 if is_custom_filter
762 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) = #{value.first.to_i})"
773 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) = #{value.first.to_i})"
763 else
774 else
764 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
775 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
765 end
776 end
766 when :float
777 when :float
767 if is_custom_filter
778 if is_custom_filter
768 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})"
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 else
780 else
770 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
781 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
771 end
782 end
772 else
783 else
773 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
784 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
774 end
785 end
775 else
786 else
776 # IN an empty set
787 # IN an empty set
777 sql = "1=0"
788 sql = "1=0"
778 end
789 end
779 when "!"
790 when "!"
780 if value.any?
791 if value.any?
781 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
792 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
782 else
793 else
783 # NOT IN an empty set
794 # NOT IN an empty set
784 sql = "1=1"
795 sql = "1=1"
785 end
796 end
786 when "!*"
797 when "!*"
787 sql = "#{db_table}.#{db_field} IS NULL"
798 sql = "#{db_table}.#{db_field} IS NULL"
788 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
799 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
789 when "*"
800 when "*"
790 sql = "#{db_table}.#{db_field} IS NOT NULL"
801 sql = "#{db_table}.#{db_field} IS NOT NULL"
791 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
802 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
792 when ">="
803 when ">="
793 if [:date, :date_past].include?(type_for(field))
804 if [:date, :date_past].include?(type_for(field))
794 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
805 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
795 else
806 else
796 if is_custom_filter
807 if is_custom_filter
797 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f})"
808 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f})"
798 else
809 else
799 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
810 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
800 end
811 end
801 end
812 end
802 when "<="
813 when "<="
803 if [:date, :date_past].include?(type_for(field))
814 if [:date, :date_past].include?(type_for(field))
804 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
815 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
805 else
816 else
806 if is_custom_filter
817 if is_custom_filter
807 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f})"
818 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f})"
808 else
819 else
809 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
820 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
810 end
821 end
811 end
822 end
812 when "><"
823 when "><"
813 if [:date, :date_past].include?(type_for(field))
824 if [:date, :date_past].include?(type_for(field))
814 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
825 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
815 else
826 else
816 if is_custom_filter
827 if is_custom_filter
817 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})"
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 else
829 else
819 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
830 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
820 end
831 end
821 end
832 end
822 when "o"
833 when "o"
823 sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id"
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 when "c"
835 when "c"
825 sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id"
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 when ">t-"
837 when ">t-"
827 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
838 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
828 when "<t-"
839 when "<t-"
829 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
840 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
830 when "t-"
841 when "t-"
831 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
842 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
832 when ">t+"
843 when ">t+"
833 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
844 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
834 when "<t+"
845 when "<t+"
835 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
846 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
836 when "t+"
847 when "t+"
837 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
848 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
838 when "t"
849 when "t"
839 sql = relative_date_clause(db_table, db_field, 0, 0)
850 sql = relative_date_clause(db_table, db_field, 0, 0)
840 when "w"
851 when "w"
841 first_day_of_week = l(:general_first_day_of_week).to_i
852 first_day_of_week = l(:general_first_day_of_week).to_i
842 day_of_week = Date.today.cwday
853 day_of_week = Date.today.cwday
843 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
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 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
855 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
845 when "~"
856 when "~"
846 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
857 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
847 when "!~"
858 when "!~"
848 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
859 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
849 else
860 else
850 raise "Unknown query operator #{operator}"
861 raise "Unknown query operator #{operator}"
851 end
862 end
852
863
853 return sql
864 return sql
854 end
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 @available_filters ||= {}
869 @available_filters ||= {}
858
870
859 custom_fields.select(&:is_filter?).each do |field|
871 custom_fields.select(&:is_filter?).each do |field|
860 case field.field_format
872 case field.field_format
861 when "text"
873 when "text"
862 options = { :type => :text, :order => 20 }
874 options = { :type => :text, :order => 20 }
863 when "list"
875 when "list"
864 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
876 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
865 when "date"
877 when "date"
866 options = { :type => :date, :order => 20 }
878 options = { :type => :date, :order => 20 }
867 when "bool"
879 when "bool"
868 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
880 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
869 when "int"
881 when "int"
870 options = { :type => :integer, :order => 20 }
882 options = { :type => :integer, :order => 20 }
871 when "float"
883 when "float"
872 options = { :type => :float, :order => 20 }
884 options = { :type => :float, :order => 20 }
873 when "user", "version"
885 when "user", "version"
874 next unless project
886 next unless project
875 values = field.possible_values_options(project)
887 values = field.possible_values_options(project)
876 if User.current.logged? && field.field_format == 'user'
888 if User.current.logged? && field.field_format == 'user'
877 values.unshift ["<< #{l(:label_me)} >>", "me"]
889 values.unshift ["<< #{l(:label_me)} >>", "me"]
878 end
890 end
879 options = { :type => :list_optional, :values => values, :order => 20}
891 options = { :type => :list_optional, :values => values, :order => 20}
880 else
892 else
881 options = { :type => :string, :order => 20 }
893 options = { :type => :string, :order => 20 }
882 end
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 end
914 end
885 end
915 end
886
916
887 # Returns a SQL clause for a date or datetime field.
917 # Returns a SQL clause for a date or datetime field.
888 def date_clause(table, field, from, to)
918 def date_clause(table, field, from, to)
889 s = []
919 s = []
890 if from
920 if from
891 from_yesterday = from - 1
921 from_yesterday = from - 1
892 from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
922 from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
893 if self.class.default_timezone == :utc
923 if self.class.default_timezone == :utc
894 from_yesterday_time = from_yesterday_time.utc
924 from_yesterday_time = from_yesterday_time.utc
895 end
925 end
896 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)])
926 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)])
897 end
927 end
898 if to
928 if to
899 to_time = Time.local(to.year, to.month, to.day)
929 to_time = Time.local(to.year, to.month, to.day)
900 if self.class.default_timezone == :utc
930 if self.class.default_timezone == :utc
901 to_time = to_time.utc
931 to_time = to_time.utc
902 end
932 end
903 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
933 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
904 end
934 end
905 s.join(' AND ')
935 s.join(' AND ')
906 end
936 end
907
937
908 # Returns a SQL clause for a date or datetime field using relative dates.
938 # Returns a SQL clause for a date or datetime field using relative dates.
909 def relative_date_clause(table, field, days_from, days_to)
939 def relative_date_clause(table, field, days_from, days_to)
910 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
940 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
911 end
941 end
912
942
913 # Additional joins required for the given sort options
943 # Additional joins required for the given sort options
914 def joins_for_order_statement(order_options)
944 def joins_for_order_statement(order_options)
915 joins = []
945 joins = []
916
946
917 if order_options
947 if order_options
918 if order_options.include?('authors')
948 if order_options.include?('authors')
919 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{Issue.table_name}.author_id"
949 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{Issue.table_name}.author_id"
920 end
950 end
921 order_options.scan(/cf_\d+/).uniq.each do |name|
951 order_options.scan(/cf_\d+/).uniq.each do |name|
922 column = available_columns.detect {|c| c.name.to_s == name}
952 column = available_columns.detect {|c| c.name.to_s == name}
923 join = column && column.custom_field.join_for_order_statement
953 join = column && column.custom_field.join_for_order_statement
924 if join
954 if join
925 joins << join
955 joins << join
926 end
956 end
927 end
957 end
928 end
958 end
929
959
930 joins.any? ? joins.join(' ') : nil
960 joins.any? ? joins.join(' ') : nil
931 end
961 end
932 end
962 end
@@ -1,72 +1,82
1 <%= error_messages_for 'custom_field' %>
1 <%= error_messages_for 'custom_field' %>
2
2
3 <div class="box tabular">
3 <div class="box tabular">
4 <p><%= f.text_field :name, :required => true %></p>
4 <p><%= f.text_field :name, :required => true %></p>
5 <p><%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :disabled => !@custom_field.new_record? %></p>
5 <p><%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :disabled => !@custom_field.new_record? %></p>
6
6
7 <% if @custom_field.format_in? 'list', 'user', 'version' %>
7 <% if @custom_field.format_in? 'list', 'user', 'version' %>
8 <p><%= f.check_box :multiple, :disabled => @custom_field.multiple && !@custom_field.new_record? %></p>
8 <p><%= f.check_box :multiple, :disabled => @custom_field.multiple && !@custom_field.new_record? %></p>
9 <% end %>
9 <% end %>
10
10
11 <% unless @custom_field.format_in? 'list', 'bool', 'date', 'user', 'version' %>
11 <% unless @custom_field.format_in? 'list', 'bool', 'date', 'user', 'version' %>
12 <p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
12 <p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
13 <%= f.text_field :min_length, :size => 5, :no_label => true %> -
13 <%= f.text_field :min_length, :size => 5, :no_label => true %> -
14 <%= f.text_field :max_length, :size => 5, :no_label => true %><br />(<%=l(:text_min_max_length_info)%>)</p>
14 <%= f.text_field :max_length, :size => 5, :no_label => true %><br />(<%=l(:text_min_max_length_info)%>)</p>
15 <p><%= f.text_field :regexp, :size => 50 %><br />(<%=l(:text_regexp_info)%>)</p>
15 <p><%= f.text_field :regexp, :size => 50 %><br />(<%=l(:text_regexp_info)%>)</p>
16 <% end %>
16 <% end %>
17
17
18 <% if @custom_field.format_in? 'list' %>
18 <% if @custom_field.format_in? 'list' %>
19 <p>
19 <p>
20 <%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %>
20 <%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %>
21 <em class="info"><%= l(:text_custom_field_possible_values_info) %></em>
21 <em class="info"><%= l(:text_custom_field_possible_values_info) %></em>
22 </p>
22 </p>
23 <% end %>
23 <% end %>
24
24
25 <% unless @custom_field.format_in? 'user', 'version' %>
25 <% unless @custom_field.format_in? 'user', 'version' %>
26 <p><%= @custom_field.field_format == 'bool' ? f.check_box(:default_value) : f.text_field(:default_value) %></p>
26 <p><%= @custom_field.field_format == 'bool' ? f.check_box(:default_value) : f.text_field(:default_value) %></p>
27 <% end %>
27 <% end %>
28
28
29 <%= call_hook(:view_custom_fields_form_upper_box, :custom_field => @custom_field, :form => f) %>
29 <%= call_hook(:view_custom_fields_form_upper_box, :custom_field => @custom_field, :form => f) %>
30 </div>
30 </div>
31
31
32 <div class="box tabular">
32 <div class="box tabular">
33 <% case @custom_field.class.name
33 <% case @custom_field.class.name
34 when "IssueCustomField" %>
34 when "IssueCustomField" %>
35
35
36 <fieldset><legend><%=l(:label_tracker_plural)%></legend>
36 <fieldset><legend><%=l(:label_tracker_plural)%></legend>
37 <% Tracker.sorted.all.each do |tracker| %>
37 <% Tracker.sorted.all.each do |tracker| %>
38 <%= check_box_tag "custom_field[tracker_ids][]",
38 <%= check_box_tag "custom_field[tracker_ids][]",
39 tracker.id,
39 tracker.id,
40 (@custom_field.trackers.include? tracker),
40 (@custom_field.trackers.include? tracker),
41 :id => "custom_field_tracker_ids_#{tracker.id}" %>
41 :id => "custom_field_tracker_ids_#{tracker.id}" %>
42 <label class="no-css" for="custom_field_tracker_ids_<%=tracker.id%>">
42 <label class="no-css" for="custom_field_tracker_ids_<%=tracker.id%>">
43 <%= h(tracker.name) %>
43 <%= h(tracker.name) %>
44 </label>
44 </label>
45 <% end %>
45 <% end %>
46 <%= hidden_field_tag "custom_field[tracker_ids][]", '' %>
46 <%= hidden_field_tag "custom_field[tracker_ids][]", '' %>
47 </fieldset>
47 </fieldset>
48 &nbsp;
48 &nbsp;
49 <p><%= f.check_box :is_required %></p>
49 <p><%= f.check_box :is_required %></p>
50 <p><%= f.check_box :is_for_all %></p>
50 <p><%= f.check_box :is_for_all %></p>
51 <p><%= f.check_box :is_filter %></p>
51 <p><%= f.check_box :is_filter %></p>
52 <p><%= f.check_box :searchable %></p>
52 <p><%= f.check_box :searchable %></p>
53
53
54 <% when "UserCustomField" %>
54 <% when "UserCustomField" %>
55 <p><%= f.check_box :is_required %></p>
55 <p><%= f.check_box :is_required %></p>
56 <p><%= f.check_box :visible %></p>
56 <p><%= f.check_box :visible %></p>
57 <p><%= f.check_box :editable %></p>
57 <p><%= f.check_box :editable %></p>
58 <p><%= f.check_box :is_filter %></p>
58
59
59 <% when "ProjectCustomField" %>
60 <% when "ProjectCustomField" %>
60 <p><%= f.check_box :is_required %></p>
61 <p><%= f.check_box :is_required %></p>
61 <p><%= f.check_box :visible %></p>
62 <p><%= f.check_box :visible %></p>
62 <p><%= f.check_box :searchable %></p>
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 <% when "TimeEntryCustomField" %>
74 <% when "TimeEntryCustomField" %>
65 <p><%= f.check_box :is_required %></p>
75 <p><%= f.check_box :is_required %></p>
66
76
67 <% else %>
77 <% else %>
68 <p><%= f.check_box :is_required %></p>
78 <p><%= f.check_box :is_required %></p>
69
79
70 <% end %>
80 <% end %>
71 <%= call_hook(:"view_custom_fields_form_#{@custom_field.type.to_s.underscore}", :custom_field => @custom_field, :form => f) %>
81 <%= call_hook(:"view_custom_fields_form_#{@custom_field.type.to_s.underscore}", :custom_field => @custom_field, :form => f) %>
72 </div>
82 </div>
@@ -1,1057 +1,1061
1 en:
1 en:
2 # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl)
2 # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl)
3 direction: ltr
3 direction: ltr
4 date:
4 date:
5 formats:
5 formats:
6 # Use the strftime parameters for formats.
6 # Use the strftime parameters for formats.
7 # When no format has been given, it uses default.
7 # When no format has been given, it uses default.
8 # You can provide other formats here if you like!
8 # You can provide other formats here if you like!
9 default: "%m/%d/%Y"
9 default: "%m/%d/%Y"
10 short: "%b %d"
10 short: "%b %d"
11 long: "%B %d, %Y"
11 long: "%B %d, %Y"
12
12
13 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
13 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
14 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
14 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
15
15
16 # Don't forget the nil at the beginning; there's no such thing as a 0th month
16 # Don't forget the nil at the beginning; there's no such thing as a 0th month
17 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
17 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
18 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
18 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
19 # Used in date_select and datime_select.
19 # Used in date_select and datime_select.
20 order:
20 order:
21 - :year
21 - :year
22 - :month
22 - :month
23 - :day
23 - :day
24
24
25 time:
25 time:
26 formats:
26 formats:
27 default: "%m/%d/%Y %I:%M %p"
27 default: "%m/%d/%Y %I:%M %p"
28 time: "%I:%M %p"
28 time: "%I:%M %p"
29 short: "%d %b %H:%M"
29 short: "%d %b %H:%M"
30 long: "%B %d, %Y %H:%M"
30 long: "%B %d, %Y %H:%M"
31 am: "am"
31 am: "am"
32 pm: "pm"
32 pm: "pm"
33
33
34 datetime:
34 datetime:
35 distance_in_words:
35 distance_in_words:
36 half_a_minute: "half a minute"
36 half_a_minute: "half a minute"
37 less_than_x_seconds:
37 less_than_x_seconds:
38 one: "less than 1 second"
38 one: "less than 1 second"
39 other: "less than %{count} seconds"
39 other: "less than %{count} seconds"
40 x_seconds:
40 x_seconds:
41 one: "1 second"
41 one: "1 second"
42 other: "%{count} seconds"
42 other: "%{count} seconds"
43 less_than_x_minutes:
43 less_than_x_minutes:
44 one: "less than a minute"
44 one: "less than a minute"
45 other: "less than %{count} minutes"
45 other: "less than %{count} minutes"
46 x_minutes:
46 x_minutes:
47 one: "1 minute"
47 one: "1 minute"
48 other: "%{count} minutes"
48 other: "%{count} minutes"
49 about_x_hours:
49 about_x_hours:
50 one: "about 1 hour"
50 one: "about 1 hour"
51 other: "about %{count} hours"
51 other: "about %{count} hours"
52 x_hours:
52 x_hours:
53 one: "1 hour"
53 one: "1 hour"
54 other: "%{count} hours"
54 other: "%{count} hours"
55 x_days:
55 x_days:
56 one: "1 day"
56 one: "1 day"
57 other: "%{count} days"
57 other: "%{count} days"
58 about_x_months:
58 about_x_months:
59 one: "about 1 month"
59 one: "about 1 month"
60 other: "about %{count} months"
60 other: "about %{count} months"
61 x_months:
61 x_months:
62 one: "1 month"
62 one: "1 month"
63 other: "%{count} months"
63 other: "%{count} months"
64 about_x_years:
64 about_x_years:
65 one: "about 1 year"
65 one: "about 1 year"
66 other: "about %{count} years"
66 other: "about %{count} years"
67 over_x_years:
67 over_x_years:
68 one: "over 1 year"
68 one: "over 1 year"
69 other: "over %{count} years"
69 other: "over %{count} years"
70 almost_x_years:
70 almost_x_years:
71 one: "almost 1 year"
71 one: "almost 1 year"
72 other: "almost %{count} years"
72 other: "almost %{count} years"
73
73
74 number:
74 number:
75 format:
75 format:
76 separator: "."
76 separator: "."
77 delimiter: ""
77 delimiter: ""
78 precision: 3
78 precision: 3
79
79
80 human:
80 human:
81 format:
81 format:
82 delimiter: ""
82 delimiter: ""
83 precision: 3
83 precision: 3
84 storage_units:
84 storage_units:
85 format: "%n %u"
85 format: "%n %u"
86 units:
86 units:
87 byte:
87 byte:
88 one: "Byte"
88 one: "Byte"
89 other: "Bytes"
89 other: "Bytes"
90 kb: "KB"
90 kb: "KB"
91 mb: "MB"
91 mb: "MB"
92 gb: "GB"
92 gb: "GB"
93 tb: "TB"
93 tb: "TB"
94
94
95 # Used in array.to_sentence.
95 # Used in array.to_sentence.
96 support:
96 support:
97 array:
97 array:
98 sentence_connector: "and"
98 sentence_connector: "and"
99 skip_last_comma: false
99 skip_last_comma: false
100
100
101 activerecord:
101 activerecord:
102 errors:
102 errors:
103 template:
103 template:
104 header:
104 header:
105 one: "1 error prohibited this %{model} from being saved"
105 one: "1 error prohibited this %{model} from being saved"
106 other: "%{count} errors prohibited this %{model} from being saved"
106 other: "%{count} errors prohibited this %{model} from being saved"
107 messages:
107 messages:
108 inclusion: "is not included in the list"
108 inclusion: "is not included in the list"
109 exclusion: "is reserved"
109 exclusion: "is reserved"
110 invalid: "is invalid"
110 invalid: "is invalid"
111 confirmation: "doesn't match confirmation"
111 confirmation: "doesn't match confirmation"
112 accepted: "must be accepted"
112 accepted: "must be accepted"
113 empty: "can't be empty"
113 empty: "can't be empty"
114 blank: "can't be blank"
114 blank: "can't be blank"
115 too_long: "is too long (maximum is %{count} characters)"
115 too_long: "is too long (maximum is %{count} characters)"
116 too_short: "is too short (minimum is %{count} characters)"
116 too_short: "is too short (minimum is %{count} characters)"
117 wrong_length: "is the wrong length (should be %{count} characters)"
117 wrong_length: "is the wrong length (should be %{count} characters)"
118 taken: "has already been taken"
118 taken: "has already been taken"
119 not_a_number: "is not a number"
119 not_a_number: "is not a number"
120 not_a_date: "is not a valid date"
120 not_a_date: "is not a valid date"
121 greater_than: "must be greater than %{count}"
121 greater_than: "must be greater than %{count}"
122 greater_than_or_equal_to: "must be greater than or equal to %{count}"
122 greater_than_or_equal_to: "must be greater than or equal to %{count}"
123 equal_to: "must be equal to %{count}"
123 equal_to: "must be equal to %{count}"
124 less_than: "must be less than %{count}"
124 less_than: "must be less than %{count}"
125 less_than_or_equal_to: "must be less than or equal to %{count}"
125 less_than_or_equal_to: "must be less than or equal to %{count}"
126 odd: "must be odd"
126 odd: "must be odd"
127 even: "must be even"
127 even: "must be even"
128 greater_than_start_date: "must be greater than start date"
128 greater_than_start_date: "must be greater than start date"
129 not_same_project: "doesn't belong to the same project"
129 not_same_project: "doesn't belong to the same project"
130 circular_dependency: "This relation would create a circular dependency"
130 circular_dependency: "This relation would create a circular dependency"
131 cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks"
131 cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks"
132
132
133 actionview_instancetag_blank_option: Please select
133 actionview_instancetag_blank_option: Please select
134
134
135 general_text_No: 'No'
135 general_text_No: 'No'
136 general_text_Yes: 'Yes'
136 general_text_Yes: 'Yes'
137 general_text_no: 'no'
137 general_text_no: 'no'
138 general_text_yes: 'yes'
138 general_text_yes: 'yes'
139 general_lang_name: 'English'
139 general_lang_name: 'English'
140 general_csv_separator: ','
140 general_csv_separator: ','
141 general_csv_decimal_separator: '.'
141 general_csv_decimal_separator: '.'
142 general_csv_encoding: ISO-8859-1
142 general_csv_encoding: ISO-8859-1
143 general_pdf_encoding: UTF-8
143 general_pdf_encoding: UTF-8
144 general_first_day_of_week: '7'
144 general_first_day_of_week: '7'
145
145
146 notice_account_updated: Account was successfully updated.
146 notice_account_updated: Account was successfully updated.
147 notice_account_invalid_creditentials: Invalid user or password
147 notice_account_invalid_creditentials: Invalid user or password
148 notice_account_password_updated: Password was successfully updated.
148 notice_account_password_updated: Password was successfully updated.
149 notice_account_wrong_password: Wrong password
149 notice_account_wrong_password: Wrong password
150 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
150 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
151 notice_account_unknown_email: Unknown user.
151 notice_account_unknown_email: Unknown user.
152 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
152 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
153 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
153 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
154 notice_account_activated: Your account has been activated. You can now log in.
154 notice_account_activated: Your account has been activated. You can now log in.
155 notice_successful_create: Successful creation.
155 notice_successful_create: Successful creation.
156 notice_successful_update: Successful update.
156 notice_successful_update: Successful update.
157 notice_successful_delete: Successful deletion.
157 notice_successful_delete: Successful deletion.
158 notice_successful_connection: Successful connection.
158 notice_successful_connection: Successful connection.
159 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
159 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
160 notice_locking_conflict: Data has been updated by another user.
160 notice_locking_conflict: Data has been updated by another user.
161 notice_not_authorized: You are not authorized to access this page.
161 notice_not_authorized: You are not authorized to access this page.
162 notice_not_authorized_archived_project: The project you're trying to access has been archived.
162 notice_not_authorized_archived_project: The project you're trying to access has been archived.
163 notice_email_sent: "An email was sent to %{value}"
163 notice_email_sent: "An email was sent to %{value}"
164 notice_email_error: "An error occurred while sending mail (%{value})"
164 notice_email_error: "An error occurred while sending mail (%{value})"
165 notice_feeds_access_key_reseted: Your RSS access key was reset.
165 notice_feeds_access_key_reseted: Your RSS access key was reset.
166 notice_api_access_key_reseted: Your API access key was reset.
166 notice_api_access_key_reseted: Your API access key was reset.
167 notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}."
167 notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}."
168 notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
168 notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
169 notice_failed_to_save_members: "Failed to save member(s): %{errors}."
169 notice_failed_to_save_members: "Failed to save member(s): %{errors}."
170 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
170 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
171 notice_account_pending: "Your account was created and is now pending administrator approval."
171 notice_account_pending: "Your account was created and is now pending administrator approval."
172 notice_default_data_loaded: Default configuration successfully loaded.
172 notice_default_data_loaded: Default configuration successfully loaded.
173 notice_unable_delete_version: Unable to delete version.
173 notice_unable_delete_version: Unable to delete version.
174 notice_unable_delete_time_entry: Unable to delete time log entry.
174 notice_unable_delete_time_entry: Unable to delete time log entry.
175 notice_issue_done_ratios_updated: Issue done ratios updated.
175 notice_issue_done_ratios_updated: Issue done ratios updated.
176 notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
176 notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
177 notice_issue_successful_create: "Issue %{id} created."
177 notice_issue_successful_create: "Issue %{id} created."
178 notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it."
178 notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it."
179 notice_account_deleted: "Your account has been permanently deleted."
179 notice_account_deleted: "Your account has been permanently deleted."
180 notice_user_successful_create: "User %{id} created."
180 notice_user_successful_create: "User %{id} created."
181
181
182 error_can_t_load_default_data: "Default configuration could not be loaded: %{value}"
182 error_can_t_load_default_data: "Default configuration could not be loaded: %{value}"
183 error_scm_not_found: "The entry or revision was not found in the repository."
183 error_scm_not_found: "The entry or revision was not found in the repository."
184 error_scm_command_failed: "An error occurred when trying to access the repository: %{value}"
184 error_scm_command_failed: "An error occurred when trying to access the repository: %{value}"
185 error_scm_annotate: "The entry does not exist or cannot be annotated."
185 error_scm_annotate: "The entry does not exist or cannot be annotated."
186 error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size."
186 error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size."
187 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
187 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
188 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
188 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
189 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
189 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
190 error_can_not_delete_custom_field: Unable to delete custom field
190 error_can_not_delete_custom_field: Unable to delete custom field
191 error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted."
191 error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted."
192 error_can_not_remove_role: "This role is in use and cannot be deleted."
192 error_can_not_remove_role: "This role is in use and cannot be deleted."
193 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened'
193 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened'
194 error_can_not_archive_project: This project cannot be archived
194 error_can_not_archive_project: This project cannot be archived
195 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
195 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
196 error_workflow_copy_source: 'Please select a source tracker or role'
196 error_workflow_copy_source: 'Please select a source tracker or role'
197 error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
197 error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
198 error_unable_delete_issue_status: 'Unable to delete issue status'
198 error_unable_delete_issue_status: 'Unable to delete issue status'
199 error_unable_to_connect: "Unable to connect (%{value})"
199 error_unable_to_connect: "Unable to connect (%{value})"
200 error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})"
200 error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})"
201 error_session_expired: "Your session has expired. Please login again."
201 error_session_expired: "Your session has expired. Please login again."
202 warning_attachments_not_saved: "%{count} file(s) could not be saved."
202 warning_attachments_not_saved: "%{count} file(s) could not be saved."
203
203
204 mail_subject_lost_password: "Your %{value} password"
204 mail_subject_lost_password: "Your %{value} password"
205 mail_body_lost_password: 'To change your password, click on the following link:'
205 mail_body_lost_password: 'To change your password, click on the following link:'
206 mail_subject_register: "Your %{value} account activation"
206 mail_subject_register: "Your %{value} account activation"
207 mail_body_register: 'To activate your account, click on the following link:'
207 mail_body_register: 'To activate your account, click on the following link:'
208 mail_body_account_information_external: "You can use your %{value} account to log in."
208 mail_body_account_information_external: "You can use your %{value} account to log in."
209 mail_body_account_information: Your account information
209 mail_body_account_information: Your account information
210 mail_subject_account_activation_request: "%{value} account activation request"
210 mail_subject_account_activation_request: "%{value} account activation request"
211 mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:"
211 mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:"
212 mail_subject_reminder: "%{count} issue(s) due in the next %{days} days"
212 mail_subject_reminder: "%{count} issue(s) due in the next %{days} days"
213 mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:"
213 mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:"
214 mail_subject_wiki_content_added: "'%{id}' wiki page has been added"
214 mail_subject_wiki_content_added: "'%{id}' wiki page has been added"
215 mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}."
215 mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}."
216 mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated"
216 mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated"
217 mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}."
217 mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}."
218
218
219 gui_validation_error: 1 error
219 gui_validation_error: 1 error
220 gui_validation_error_plural: "%{count} errors"
220 gui_validation_error_plural: "%{count} errors"
221
221
222 field_name: Name
222 field_name: Name
223 field_description: Description
223 field_description: Description
224 field_summary: Summary
224 field_summary: Summary
225 field_is_required: Required
225 field_is_required: Required
226 field_firstname: First name
226 field_firstname: First name
227 field_lastname: Last name
227 field_lastname: Last name
228 field_mail: Email
228 field_mail: Email
229 field_filename: File
229 field_filename: File
230 field_filesize: Size
230 field_filesize: Size
231 field_downloads: Downloads
231 field_downloads: Downloads
232 field_author: Author
232 field_author: Author
233 field_created_on: Created
233 field_created_on: Created
234 field_updated_on: Updated
234 field_updated_on: Updated
235 field_field_format: Format
235 field_field_format: Format
236 field_is_for_all: For all projects
236 field_is_for_all: For all projects
237 field_possible_values: Possible values
237 field_possible_values: Possible values
238 field_regexp: Regular expression
238 field_regexp: Regular expression
239 field_min_length: Minimum length
239 field_min_length: Minimum length
240 field_max_length: Maximum length
240 field_max_length: Maximum length
241 field_value: Value
241 field_value: Value
242 field_category: Category
242 field_category: Category
243 field_title: Title
243 field_title: Title
244 field_project: Project
244 field_project: Project
245 field_issue: Issue
245 field_issue: Issue
246 field_status: Status
246 field_status: Status
247 field_notes: Notes
247 field_notes: Notes
248 field_is_closed: Issue closed
248 field_is_closed: Issue closed
249 field_is_default: Default value
249 field_is_default: Default value
250 field_tracker: Tracker
250 field_tracker: Tracker
251 field_subject: Subject
251 field_subject: Subject
252 field_due_date: Due date
252 field_due_date: Due date
253 field_assigned_to: Assignee
253 field_assigned_to: Assignee
254 field_priority: Priority
254 field_priority: Priority
255 field_fixed_version: Target version
255 field_fixed_version: Target version
256 field_user: User
256 field_user: User
257 field_principal: Principal
257 field_principal: Principal
258 field_role: Role
258 field_role: Role
259 field_homepage: Homepage
259 field_homepage: Homepage
260 field_is_public: Public
260 field_is_public: Public
261 field_parent: Subproject of
261 field_parent: Subproject of
262 field_is_in_roadmap: Issues displayed in roadmap
262 field_is_in_roadmap: Issues displayed in roadmap
263 field_login: Login
263 field_login: Login
264 field_mail_notification: Email notifications
264 field_mail_notification: Email notifications
265 field_admin: Administrator
265 field_admin: Administrator
266 field_last_login_on: Last connection
266 field_last_login_on: Last connection
267 field_language: Language
267 field_language: Language
268 field_effective_date: Date
268 field_effective_date: Date
269 field_password: Password
269 field_password: Password
270 field_new_password: New password
270 field_new_password: New password
271 field_password_confirmation: Confirmation
271 field_password_confirmation: Confirmation
272 field_version: Version
272 field_version: Version
273 field_type: Type
273 field_type: Type
274 field_host: Host
274 field_host: Host
275 field_port: Port
275 field_port: Port
276 field_account: Account
276 field_account: Account
277 field_base_dn: Base DN
277 field_base_dn: Base DN
278 field_attr_login: Login attribute
278 field_attr_login: Login attribute
279 field_attr_firstname: Firstname attribute
279 field_attr_firstname: Firstname attribute
280 field_attr_lastname: Lastname attribute
280 field_attr_lastname: Lastname attribute
281 field_attr_mail: Email attribute
281 field_attr_mail: Email attribute
282 field_onthefly: On-the-fly user creation
282 field_onthefly: On-the-fly user creation
283 field_start_date: Start date
283 field_start_date: Start date
284 field_done_ratio: "% Done"
284 field_done_ratio: "% Done"
285 field_auth_source: Authentication mode
285 field_auth_source: Authentication mode
286 field_hide_mail: Hide my email address
286 field_hide_mail: Hide my email address
287 field_comments: Comment
287 field_comments: Comment
288 field_url: URL
288 field_url: URL
289 field_start_page: Start page
289 field_start_page: Start page
290 field_subproject: Subproject
290 field_subproject: Subproject
291 field_hours: Hours
291 field_hours: Hours
292 field_activity: Activity
292 field_activity: Activity
293 field_spent_on: Date
293 field_spent_on: Date
294 field_identifier: Identifier
294 field_identifier: Identifier
295 field_is_filter: Used as a filter
295 field_is_filter: Used as a filter
296 field_issue_to: Related issue
296 field_issue_to: Related issue
297 field_delay: Delay
297 field_delay: Delay
298 field_assignable: Issues can be assigned to this role
298 field_assignable: Issues can be assigned to this role
299 field_redirect_existing_links: Redirect existing links
299 field_redirect_existing_links: Redirect existing links
300 field_estimated_hours: Estimated time
300 field_estimated_hours: Estimated time
301 field_column_names: Columns
301 field_column_names: Columns
302 field_time_entries: Log time
302 field_time_entries: Log time
303 field_time_zone: Time zone
303 field_time_zone: Time zone
304 field_searchable: Searchable
304 field_searchable: Searchable
305 field_default_value: Default value
305 field_default_value: Default value
306 field_comments_sorting: Display comments
306 field_comments_sorting: Display comments
307 field_parent_title: Parent page
307 field_parent_title: Parent page
308 field_editable: Editable
308 field_editable: Editable
309 field_watcher: Watcher
309 field_watcher: Watcher
310 field_identity_url: OpenID URL
310 field_identity_url: OpenID URL
311 field_content: Content
311 field_content: Content
312 field_group_by: Group results by
312 field_group_by: Group results by
313 field_sharing: Sharing
313 field_sharing: Sharing
314 field_parent_issue: Parent task
314 field_parent_issue: Parent task
315 field_member_of_group: "Assignee's group"
315 field_member_of_group: "Assignee's group"
316 field_assigned_to_role: "Assignee's role"
316 field_assigned_to_role: "Assignee's role"
317 field_text: Text field
317 field_text: Text field
318 field_visible: Visible
318 field_visible: Visible
319 field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
319 field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
320 field_issues_visibility: Issues visibility
320 field_issues_visibility: Issues visibility
321 field_is_private: Private
321 field_is_private: Private
322 field_commit_logs_encoding: Commit messages encoding
322 field_commit_logs_encoding: Commit messages encoding
323 field_scm_path_encoding: Path encoding
323 field_scm_path_encoding: Path encoding
324 field_path_to_repository: Path to repository
324 field_path_to_repository: Path to repository
325 field_root_directory: Root directory
325 field_root_directory: Root directory
326 field_cvsroot: CVSROOT
326 field_cvsroot: CVSROOT
327 field_cvs_module: Module
327 field_cvs_module: Module
328 field_repository_is_default: Main repository
328 field_repository_is_default: Main repository
329 field_multiple: Multiple values
329 field_multiple: Multiple values
330 field_auth_source_ldap_filter: LDAP filter
330 field_auth_source_ldap_filter: LDAP filter
331 field_core_fields: Standard fields
331 field_core_fields: Standard fields
332 field_timeout: "Timeout (in seconds)"
332 field_timeout: "Timeout (in seconds)"
333 field_board_parent: Parent forum
333 field_board_parent: Parent forum
334
334
335 setting_app_title: Application title
335 setting_app_title: Application title
336 setting_app_subtitle: Application subtitle
336 setting_app_subtitle: Application subtitle
337 setting_welcome_text: Welcome text
337 setting_welcome_text: Welcome text
338 setting_default_language: Default language
338 setting_default_language: Default language
339 setting_login_required: Authentication required
339 setting_login_required: Authentication required
340 setting_self_registration: Self-registration
340 setting_self_registration: Self-registration
341 setting_attachment_max_size: Maximum attachment size
341 setting_attachment_max_size: Maximum attachment size
342 setting_issues_export_limit: Issues export limit
342 setting_issues_export_limit: Issues export limit
343 setting_mail_from: Emission email address
343 setting_mail_from: Emission email address
344 setting_bcc_recipients: Blind carbon copy recipients (bcc)
344 setting_bcc_recipients: Blind carbon copy recipients (bcc)
345 setting_plain_text_mail: Plain text mail (no HTML)
345 setting_plain_text_mail: Plain text mail (no HTML)
346 setting_host_name: Host name and path
346 setting_host_name: Host name and path
347 setting_text_formatting: Text formatting
347 setting_text_formatting: Text formatting
348 setting_wiki_compression: Wiki history compression
348 setting_wiki_compression: Wiki history compression
349 setting_feeds_limit: Maximum number of items in Atom feeds
349 setting_feeds_limit: Maximum number of items in Atom feeds
350 setting_default_projects_public: New projects are public by default
350 setting_default_projects_public: New projects are public by default
351 setting_autofetch_changesets: Fetch commits automatically
351 setting_autofetch_changesets: Fetch commits automatically
352 setting_sys_api_enabled: Enable WS for repository management
352 setting_sys_api_enabled: Enable WS for repository management
353 setting_commit_ref_keywords: Referencing keywords
353 setting_commit_ref_keywords: Referencing keywords
354 setting_commit_fix_keywords: Fixing keywords
354 setting_commit_fix_keywords: Fixing keywords
355 setting_autologin: Autologin
355 setting_autologin: Autologin
356 setting_date_format: Date format
356 setting_date_format: Date format
357 setting_time_format: Time format
357 setting_time_format: Time format
358 setting_cross_project_issue_relations: Allow cross-project issue relations
358 setting_cross_project_issue_relations: Allow cross-project issue relations
359 setting_issue_list_default_columns: Default columns displayed on the issue list
359 setting_issue_list_default_columns: Default columns displayed on the issue list
360 setting_repositories_encodings: Attachments and repositories encodings
360 setting_repositories_encodings: Attachments and repositories encodings
361 setting_emails_header: Emails header
361 setting_emails_header: Emails header
362 setting_emails_footer: Emails footer
362 setting_emails_footer: Emails footer
363 setting_protocol: Protocol
363 setting_protocol: Protocol
364 setting_per_page_options: Objects per page options
364 setting_per_page_options: Objects per page options
365 setting_user_format: Users display format
365 setting_user_format: Users display format
366 setting_activity_days_default: Days displayed on project activity
366 setting_activity_days_default: Days displayed on project activity
367 setting_display_subprojects_issues: Display subprojects issues on main projects by default
367 setting_display_subprojects_issues: Display subprojects issues on main projects by default
368 setting_enabled_scm: Enabled SCM
368 setting_enabled_scm: Enabled SCM
369 setting_mail_handler_body_delimiters: "Truncate emails after one of these lines"
369 setting_mail_handler_body_delimiters: "Truncate emails after one of these lines"
370 setting_mail_handler_api_enabled: Enable WS for incoming emails
370 setting_mail_handler_api_enabled: Enable WS for incoming emails
371 setting_mail_handler_api_key: API key
371 setting_mail_handler_api_key: API key
372 setting_sequential_project_identifiers: Generate sequential project identifiers
372 setting_sequential_project_identifiers: Generate sequential project identifiers
373 setting_gravatar_enabled: Use Gravatar user icons
373 setting_gravatar_enabled: Use Gravatar user icons
374 setting_gravatar_default: Default Gravatar image
374 setting_gravatar_default: Default Gravatar image
375 setting_diff_max_lines_displayed: Maximum number of diff lines displayed
375 setting_diff_max_lines_displayed: Maximum number of diff lines displayed
376 setting_file_max_size_displayed: Maximum size of text files displayed inline
376 setting_file_max_size_displayed: Maximum size of text files displayed inline
377 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
377 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
378 setting_openid: Allow OpenID login and registration
378 setting_openid: Allow OpenID login and registration
379 setting_password_min_length: Minimum password length
379 setting_password_min_length: Minimum password length
380 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
380 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
381 setting_default_projects_modules: Default enabled modules for new projects
381 setting_default_projects_modules: Default enabled modules for new projects
382 setting_issue_done_ratio: Calculate the issue done ratio with
382 setting_issue_done_ratio: Calculate the issue done ratio with
383 setting_issue_done_ratio_issue_field: Use the issue field
383 setting_issue_done_ratio_issue_field: Use the issue field
384 setting_issue_done_ratio_issue_status: Use the issue status
384 setting_issue_done_ratio_issue_status: Use the issue status
385 setting_start_of_week: Start calendars on
385 setting_start_of_week: Start calendars on
386 setting_rest_api_enabled: Enable REST web service
386 setting_rest_api_enabled: Enable REST web service
387 setting_cache_formatted_text: Cache formatted text
387 setting_cache_formatted_text: Cache formatted text
388 setting_default_notification_option: Default notification option
388 setting_default_notification_option: Default notification option
389 setting_commit_logtime_enabled: Enable time logging
389 setting_commit_logtime_enabled: Enable time logging
390 setting_commit_logtime_activity_id: Activity for logged time
390 setting_commit_logtime_activity_id: Activity for logged time
391 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
391 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
392 setting_issue_group_assignment: Allow issue assignment to groups
392 setting_issue_group_assignment: Allow issue assignment to groups
393 setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
393 setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
394 setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed
394 setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed
395 setting_unsubscribe: Allow users to delete their own account
395 setting_unsubscribe: Allow users to delete their own account
396 setting_session_lifetime: Session maximum lifetime
396 setting_session_lifetime: Session maximum lifetime
397 setting_session_timeout: Session inactivity timeout
397 setting_session_timeout: Session inactivity timeout
398 setting_thumbnails_enabled: Display attachment thumbnails
398 setting_thumbnails_enabled: Display attachment thumbnails
399 setting_thumbnails_size: Thumbnails size (in pixels)
399 setting_thumbnails_size: Thumbnails size (in pixels)
400
400
401 permission_add_project: Create project
401 permission_add_project: Create project
402 permission_add_subprojects: Create subprojects
402 permission_add_subprojects: Create subprojects
403 permission_edit_project: Edit project
403 permission_edit_project: Edit project
404 permission_close_project: Close / reopen the project
404 permission_close_project: Close / reopen the project
405 permission_select_project_modules: Select project modules
405 permission_select_project_modules: Select project modules
406 permission_manage_members: Manage members
406 permission_manage_members: Manage members
407 permission_manage_project_activities: Manage project activities
407 permission_manage_project_activities: Manage project activities
408 permission_manage_versions: Manage versions
408 permission_manage_versions: Manage versions
409 permission_manage_categories: Manage issue categories
409 permission_manage_categories: Manage issue categories
410 permission_view_issues: View Issues
410 permission_view_issues: View Issues
411 permission_add_issues: Add issues
411 permission_add_issues: Add issues
412 permission_edit_issues: Edit issues
412 permission_edit_issues: Edit issues
413 permission_manage_issue_relations: Manage issue relations
413 permission_manage_issue_relations: Manage issue relations
414 permission_set_issues_private: Set issues public or private
414 permission_set_issues_private: Set issues public or private
415 permission_set_own_issues_private: Set own issues public or private
415 permission_set_own_issues_private: Set own issues public or private
416 permission_add_issue_notes: Add notes
416 permission_add_issue_notes: Add notes
417 permission_edit_issue_notes: Edit notes
417 permission_edit_issue_notes: Edit notes
418 permission_edit_own_issue_notes: Edit own notes
418 permission_edit_own_issue_notes: Edit own notes
419 permission_move_issues: Move issues
419 permission_move_issues: Move issues
420 permission_delete_issues: Delete issues
420 permission_delete_issues: Delete issues
421 permission_manage_public_queries: Manage public queries
421 permission_manage_public_queries: Manage public queries
422 permission_save_queries: Save queries
422 permission_save_queries: Save queries
423 permission_view_gantt: View gantt chart
423 permission_view_gantt: View gantt chart
424 permission_view_calendar: View calendar
424 permission_view_calendar: View calendar
425 permission_view_issue_watchers: View watchers list
425 permission_view_issue_watchers: View watchers list
426 permission_add_issue_watchers: Add watchers
426 permission_add_issue_watchers: Add watchers
427 permission_delete_issue_watchers: Delete watchers
427 permission_delete_issue_watchers: Delete watchers
428 permission_log_time: Log spent time
428 permission_log_time: Log spent time
429 permission_view_time_entries: View spent time
429 permission_view_time_entries: View spent time
430 permission_edit_time_entries: Edit time logs
430 permission_edit_time_entries: Edit time logs
431 permission_edit_own_time_entries: Edit own time logs
431 permission_edit_own_time_entries: Edit own time logs
432 permission_manage_news: Manage news
432 permission_manage_news: Manage news
433 permission_comment_news: Comment news
433 permission_comment_news: Comment news
434 permission_manage_documents: Manage documents
434 permission_manage_documents: Manage documents
435 permission_view_documents: View documents
435 permission_view_documents: View documents
436 permission_manage_files: Manage files
436 permission_manage_files: Manage files
437 permission_view_files: View files
437 permission_view_files: View files
438 permission_manage_wiki: Manage wiki
438 permission_manage_wiki: Manage wiki
439 permission_rename_wiki_pages: Rename wiki pages
439 permission_rename_wiki_pages: Rename wiki pages
440 permission_delete_wiki_pages: Delete wiki pages
440 permission_delete_wiki_pages: Delete wiki pages
441 permission_view_wiki_pages: View wiki
441 permission_view_wiki_pages: View wiki
442 permission_view_wiki_edits: View wiki history
442 permission_view_wiki_edits: View wiki history
443 permission_edit_wiki_pages: Edit wiki pages
443 permission_edit_wiki_pages: Edit wiki pages
444 permission_delete_wiki_pages_attachments: Delete attachments
444 permission_delete_wiki_pages_attachments: Delete attachments
445 permission_protect_wiki_pages: Protect wiki pages
445 permission_protect_wiki_pages: Protect wiki pages
446 permission_manage_repository: Manage repository
446 permission_manage_repository: Manage repository
447 permission_browse_repository: Browse repository
447 permission_browse_repository: Browse repository
448 permission_view_changesets: View changesets
448 permission_view_changesets: View changesets
449 permission_commit_access: Commit access
449 permission_commit_access: Commit access
450 permission_manage_boards: Manage forums
450 permission_manage_boards: Manage forums
451 permission_view_messages: View messages
451 permission_view_messages: View messages
452 permission_add_messages: Post messages
452 permission_add_messages: Post messages
453 permission_edit_messages: Edit messages
453 permission_edit_messages: Edit messages
454 permission_edit_own_messages: Edit own messages
454 permission_edit_own_messages: Edit own messages
455 permission_delete_messages: Delete messages
455 permission_delete_messages: Delete messages
456 permission_delete_own_messages: Delete own messages
456 permission_delete_own_messages: Delete own messages
457 permission_export_wiki_pages: Export wiki pages
457 permission_export_wiki_pages: Export wiki pages
458 permission_manage_subtasks: Manage subtasks
458 permission_manage_subtasks: Manage subtasks
459 permission_manage_related_issues: Manage related issues
459 permission_manage_related_issues: Manage related issues
460
460
461 project_module_issue_tracking: Issue tracking
461 project_module_issue_tracking: Issue tracking
462 project_module_time_tracking: Time tracking
462 project_module_time_tracking: Time tracking
463 project_module_news: News
463 project_module_news: News
464 project_module_documents: Documents
464 project_module_documents: Documents
465 project_module_files: Files
465 project_module_files: Files
466 project_module_wiki: Wiki
466 project_module_wiki: Wiki
467 project_module_repository: Repository
467 project_module_repository: Repository
468 project_module_boards: Forums
468 project_module_boards: Forums
469 project_module_calendar: Calendar
469 project_module_calendar: Calendar
470 project_module_gantt: Gantt
470 project_module_gantt: Gantt
471
471
472 label_user: User
472 label_user: User
473 label_user_plural: Users
473 label_user_plural: Users
474 label_user_new: New user
474 label_user_new: New user
475 label_user_anonymous: Anonymous
475 label_user_anonymous: Anonymous
476 label_project: Project
476 label_project: Project
477 label_project_new: New project
477 label_project_new: New project
478 label_project_plural: Projects
478 label_project_plural: Projects
479 label_x_projects:
479 label_x_projects:
480 zero: no projects
480 zero: no projects
481 one: 1 project
481 one: 1 project
482 other: "%{count} projects"
482 other: "%{count} projects"
483 label_project_all: All Projects
483 label_project_all: All Projects
484 label_project_latest: Latest projects
484 label_project_latest: Latest projects
485 label_issue: Issue
485 label_issue: Issue
486 label_issue_new: New issue
486 label_issue_new: New issue
487 label_issue_plural: Issues
487 label_issue_plural: Issues
488 label_issue_view_all: View all issues
488 label_issue_view_all: View all issues
489 label_issues_by: "Issues by %{value}"
489 label_issues_by: "Issues by %{value}"
490 label_issue_added: Issue added
490 label_issue_added: Issue added
491 label_issue_updated: Issue updated
491 label_issue_updated: Issue updated
492 label_issue_note_added: Note added
492 label_issue_note_added: Note added
493 label_issue_status_updated: Status updated
493 label_issue_status_updated: Status updated
494 label_issue_priority_updated: Priority updated
494 label_issue_priority_updated: Priority updated
495 label_document: Document
495 label_document: Document
496 label_document_new: New document
496 label_document_new: New document
497 label_document_plural: Documents
497 label_document_plural: Documents
498 label_document_added: Document added
498 label_document_added: Document added
499 label_role: Role
499 label_role: Role
500 label_role_plural: Roles
500 label_role_plural: Roles
501 label_role_new: New role
501 label_role_new: New role
502 label_role_and_permissions: Roles and permissions
502 label_role_and_permissions: Roles and permissions
503 label_role_anonymous: Anonymous
503 label_role_anonymous: Anonymous
504 label_role_non_member: Non member
504 label_role_non_member: Non member
505 label_member: Member
505 label_member: Member
506 label_member_new: New member
506 label_member_new: New member
507 label_member_plural: Members
507 label_member_plural: Members
508 label_tracker: Tracker
508 label_tracker: Tracker
509 label_tracker_plural: Trackers
509 label_tracker_plural: Trackers
510 label_tracker_new: New tracker
510 label_tracker_new: New tracker
511 label_workflow: Workflow
511 label_workflow: Workflow
512 label_issue_status: Issue status
512 label_issue_status: Issue status
513 label_issue_status_plural: Issue statuses
513 label_issue_status_plural: Issue statuses
514 label_issue_status_new: New status
514 label_issue_status_new: New status
515 label_issue_category: Issue category
515 label_issue_category: Issue category
516 label_issue_category_plural: Issue categories
516 label_issue_category_plural: Issue categories
517 label_issue_category_new: New category
517 label_issue_category_new: New category
518 label_custom_field: Custom field
518 label_custom_field: Custom field
519 label_custom_field_plural: Custom fields
519 label_custom_field_plural: Custom fields
520 label_custom_field_new: New custom field
520 label_custom_field_new: New custom field
521 label_enumerations: Enumerations
521 label_enumerations: Enumerations
522 label_enumeration_new: New value
522 label_enumeration_new: New value
523 label_information: Information
523 label_information: Information
524 label_information_plural: Information
524 label_information_plural: Information
525 label_please_login: Please log in
525 label_please_login: Please log in
526 label_register: Register
526 label_register: Register
527 label_login_with_open_id_option: or login with OpenID
527 label_login_with_open_id_option: or login with OpenID
528 label_password_lost: Lost password
528 label_password_lost: Lost password
529 label_home: Home
529 label_home: Home
530 label_my_page: My page
530 label_my_page: My page
531 label_my_account: My account
531 label_my_account: My account
532 label_my_projects: My projects
532 label_my_projects: My projects
533 label_my_page_block: My page block
533 label_my_page_block: My page block
534 label_administration: Administration
534 label_administration: Administration
535 label_login: Sign in
535 label_login: Sign in
536 label_logout: Sign out
536 label_logout: Sign out
537 label_help: Help
537 label_help: Help
538 label_reported_issues: Reported issues
538 label_reported_issues: Reported issues
539 label_assigned_to_me_issues: Issues assigned to me
539 label_assigned_to_me_issues: Issues assigned to me
540 label_last_login: Last connection
540 label_last_login: Last connection
541 label_registered_on: Registered on
541 label_registered_on: Registered on
542 label_activity: Activity
542 label_activity: Activity
543 label_overall_activity: Overall activity
543 label_overall_activity: Overall activity
544 label_user_activity: "%{value}'s activity"
544 label_user_activity: "%{value}'s activity"
545 label_new: New
545 label_new: New
546 label_logged_as: Logged in as
546 label_logged_as: Logged in as
547 label_environment: Environment
547 label_environment: Environment
548 label_authentication: Authentication
548 label_authentication: Authentication
549 label_auth_source: Authentication mode
549 label_auth_source: Authentication mode
550 label_auth_source_new: New authentication mode
550 label_auth_source_new: New authentication mode
551 label_auth_source_plural: Authentication modes
551 label_auth_source_plural: Authentication modes
552 label_subproject_plural: Subprojects
552 label_subproject_plural: Subprojects
553 label_subproject_new: New subproject
553 label_subproject_new: New subproject
554 label_and_its_subprojects: "%{value} and its subprojects"
554 label_and_its_subprojects: "%{value} and its subprojects"
555 label_min_max_length: Min - Max length
555 label_min_max_length: Min - Max length
556 label_list: List
556 label_list: List
557 label_date: Date
557 label_date: Date
558 label_integer: Integer
558 label_integer: Integer
559 label_float: Float
559 label_float: Float
560 label_boolean: Boolean
560 label_boolean: Boolean
561 label_string: Text
561 label_string: Text
562 label_text: Long text
562 label_text: Long text
563 label_attribute: Attribute
563 label_attribute: Attribute
564 label_attribute_plural: Attributes
564 label_attribute_plural: Attributes
565 label_download: "%{count} Download"
565 label_download: "%{count} Download"
566 label_download_plural: "%{count} Downloads"
566 label_download_plural: "%{count} Downloads"
567 label_no_data: No data to display
567 label_no_data: No data to display
568 label_change_status: Change status
568 label_change_status: Change status
569 label_history: History
569 label_history: History
570 label_attachment: File
570 label_attachment: File
571 label_attachment_new: New file
571 label_attachment_new: New file
572 label_attachment_delete: Delete file
572 label_attachment_delete: Delete file
573 label_attachment_plural: Files
573 label_attachment_plural: Files
574 label_file_added: File added
574 label_file_added: File added
575 label_report: Report
575 label_report: Report
576 label_report_plural: Reports
576 label_report_plural: Reports
577 label_news: News
577 label_news: News
578 label_news_new: Add news
578 label_news_new: Add news
579 label_news_plural: News
579 label_news_plural: News
580 label_news_latest: Latest news
580 label_news_latest: Latest news
581 label_news_view_all: View all news
581 label_news_view_all: View all news
582 label_news_added: News added
582 label_news_added: News added
583 label_news_comment_added: Comment added to a news
583 label_news_comment_added: Comment added to a news
584 label_settings: Settings
584 label_settings: Settings
585 label_overview: Overview
585 label_overview: Overview
586 label_version: Version
586 label_version: Version
587 label_version_new: New version
587 label_version_new: New version
588 label_version_plural: Versions
588 label_version_plural: Versions
589 label_close_versions: Close completed versions
589 label_close_versions: Close completed versions
590 label_confirmation: Confirmation
590 label_confirmation: Confirmation
591 label_export_to: 'Also available in:'
591 label_export_to: 'Also available in:'
592 label_read: Read...
592 label_read: Read...
593 label_public_projects: Public projects
593 label_public_projects: Public projects
594 label_open_issues: open
594 label_open_issues: open
595 label_open_issues_plural: open
595 label_open_issues_plural: open
596 label_closed_issues: closed
596 label_closed_issues: closed
597 label_closed_issues_plural: closed
597 label_closed_issues_plural: closed
598 label_x_open_issues_abbr_on_total:
598 label_x_open_issues_abbr_on_total:
599 zero: 0 open / %{total}
599 zero: 0 open / %{total}
600 one: 1 open / %{total}
600 one: 1 open / %{total}
601 other: "%{count} open / %{total}"
601 other: "%{count} open / %{total}"
602 label_x_open_issues_abbr:
602 label_x_open_issues_abbr:
603 zero: 0 open
603 zero: 0 open
604 one: 1 open
604 one: 1 open
605 other: "%{count} open"
605 other: "%{count} open"
606 label_x_closed_issues_abbr:
606 label_x_closed_issues_abbr:
607 zero: 0 closed
607 zero: 0 closed
608 one: 1 closed
608 one: 1 closed
609 other: "%{count} closed"
609 other: "%{count} closed"
610 label_x_issues:
610 label_x_issues:
611 zero: 0 issues
611 zero: 0 issues
612 one: 1 issue
612 one: 1 issue
613 other: "%{count} issues"
613 other: "%{count} issues"
614 label_total: Total
614 label_total: Total
615 label_permissions: Permissions
615 label_permissions: Permissions
616 label_current_status: Current status
616 label_current_status: Current status
617 label_new_statuses_allowed: New statuses allowed
617 label_new_statuses_allowed: New statuses allowed
618 label_all: all
618 label_all: all
619 label_none: none
619 label_none: none
620 label_nobody: nobody
620 label_nobody: nobody
621 label_next: Next
621 label_next: Next
622 label_previous: Previous
622 label_previous: Previous
623 label_used_by: Used by
623 label_used_by: Used by
624 label_details: Details
624 label_details: Details
625 label_add_note: Add a note
625 label_add_note: Add a note
626 label_per_page: Per page
626 label_per_page: Per page
627 label_calendar: Calendar
627 label_calendar: Calendar
628 label_months_from: months from
628 label_months_from: months from
629 label_gantt: Gantt
629 label_gantt: Gantt
630 label_internal: Internal
630 label_internal: Internal
631 label_last_changes: "last %{count} changes"
631 label_last_changes: "last %{count} changes"
632 label_change_view_all: View all changes
632 label_change_view_all: View all changes
633 label_personalize_page: Personalize this page
633 label_personalize_page: Personalize this page
634 label_comment: Comment
634 label_comment: Comment
635 label_comment_plural: Comments
635 label_comment_plural: Comments
636 label_x_comments:
636 label_x_comments:
637 zero: no comments
637 zero: no comments
638 one: 1 comment
638 one: 1 comment
639 other: "%{count} comments"
639 other: "%{count} comments"
640 label_comment_add: Add a comment
640 label_comment_add: Add a comment
641 label_comment_added: Comment added
641 label_comment_added: Comment added
642 label_comment_delete: Delete comments
642 label_comment_delete: Delete comments
643 label_query: Custom query
643 label_query: Custom query
644 label_query_plural: Custom queries
644 label_query_plural: Custom queries
645 label_query_new: New query
645 label_query_new: New query
646 label_my_queries: My custom queries
646 label_my_queries: My custom queries
647 label_filter_add: Add filter
647 label_filter_add: Add filter
648 label_filter_plural: Filters
648 label_filter_plural: Filters
649 label_equals: is
649 label_equals: is
650 label_not_equals: is not
650 label_not_equals: is not
651 label_in_less_than: in less than
651 label_in_less_than: in less than
652 label_in_more_than: in more than
652 label_in_more_than: in more than
653 label_greater_or_equal: '>='
653 label_greater_or_equal: '>='
654 label_less_or_equal: '<='
654 label_less_or_equal: '<='
655 label_between: between
655 label_between: between
656 label_in: in
656 label_in: in
657 label_today: today
657 label_today: today
658 label_all_time: all time
658 label_all_time: all time
659 label_yesterday: yesterday
659 label_yesterday: yesterday
660 label_this_week: this week
660 label_this_week: this week
661 label_last_week: last week
661 label_last_week: last week
662 label_last_n_days: "last %{count} days"
662 label_last_n_days: "last %{count} days"
663 label_this_month: this month
663 label_this_month: this month
664 label_last_month: last month
664 label_last_month: last month
665 label_this_year: this year
665 label_this_year: this year
666 label_date_range: Date range
666 label_date_range: Date range
667 label_less_than_ago: less than days ago
667 label_less_than_ago: less than days ago
668 label_more_than_ago: more than days ago
668 label_more_than_ago: more than days ago
669 label_ago: days ago
669 label_ago: days ago
670 label_contains: contains
670 label_contains: contains
671 label_not_contains: doesn't contain
671 label_not_contains: doesn't contain
672 label_day_plural: days
672 label_day_plural: days
673 label_repository: Repository
673 label_repository: Repository
674 label_repository_new: New repository
674 label_repository_new: New repository
675 label_repository_plural: Repositories
675 label_repository_plural: Repositories
676 label_browse: Browse
676 label_browse: Browse
677 label_modification: "%{count} change"
677 label_modification: "%{count} change"
678 label_modification_plural: "%{count} changes"
678 label_modification_plural: "%{count} changes"
679 label_branch: Branch
679 label_branch: Branch
680 label_tag: Tag
680 label_tag: Tag
681 label_revision: Revision
681 label_revision: Revision
682 label_revision_plural: Revisions
682 label_revision_plural: Revisions
683 label_revision_id: "Revision %{value}"
683 label_revision_id: "Revision %{value}"
684 label_associated_revisions: Associated revisions
684 label_associated_revisions: Associated revisions
685 label_added: added
685 label_added: added
686 label_modified: modified
686 label_modified: modified
687 label_copied: copied
687 label_copied: copied
688 label_renamed: renamed
688 label_renamed: renamed
689 label_deleted: deleted
689 label_deleted: deleted
690 label_latest_revision: Latest revision
690 label_latest_revision: Latest revision
691 label_latest_revision_plural: Latest revisions
691 label_latest_revision_plural: Latest revisions
692 label_view_revisions: View revisions
692 label_view_revisions: View revisions
693 label_view_all_revisions: View all revisions
693 label_view_all_revisions: View all revisions
694 label_max_size: Maximum size
694 label_max_size: Maximum size
695 label_sort_highest: Move to top
695 label_sort_highest: Move to top
696 label_sort_higher: Move up
696 label_sort_higher: Move up
697 label_sort_lower: Move down
697 label_sort_lower: Move down
698 label_sort_lowest: Move to bottom
698 label_sort_lowest: Move to bottom
699 label_roadmap: Roadmap
699 label_roadmap: Roadmap
700 label_roadmap_due_in: "Due in %{value}"
700 label_roadmap_due_in: "Due in %{value}"
701 label_roadmap_overdue: "%{value} late"
701 label_roadmap_overdue: "%{value} late"
702 label_roadmap_no_issues: No issues for this version
702 label_roadmap_no_issues: No issues for this version
703 label_search: Search
703 label_search: Search
704 label_result_plural: Results
704 label_result_plural: Results
705 label_all_words: All words
705 label_all_words: All words
706 label_wiki: Wiki
706 label_wiki: Wiki
707 label_wiki_edit: Wiki edit
707 label_wiki_edit: Wiki edit
708 label_wiki_edit_plural: Wiki edits
708 label_wiki_edit_plural: Wiki edits
709 label_wiki_page: Wiki page
709 label_wiki_page: Wiki page
710 label_wiki_page_plural: Wiki pages
710 label_wiki_page_plural: Wiki pages
711 label_index_by_title: Index by title
711 label_index_by_title: Index by title
712 label_index_by_date: Index by date
712 label_index_by_date: Index by date
713 label_current_version: Current version
713 label_current_version: Current version
714 label_preview: Preview
714 label_preview: Preview
715 label_feed_plural: Feeds
715 label_feed_plural: Feeds
716 label_changes_details: Details of all changes
716 label_changes_details: Details of all changes
717 label_issue_tracking: Issue tracking
717 label_issue_tracking: Issue tracking
718 label_spent_time: Spent time
718 label_spent_time: Spent time
719 label_overall_spent_time: Overall spent time
719 label_overall_spent_time: Overall spent time
720 label_f_hour: "%{value} hour"
720 label_f_hour: "%{value} hour"
721 label_f_hour_plural: "%{value} hours"
721 label_f_hour_plural: "%{value} hours"
722 label_time_tracking: Time tracking
722 label_time_tracking: Time tracking
723 label_change_plural: Changes
723 label_change_plural: Changes
724 label_statistics: Statistics
724 label_statistics: Statistics
725 label_commits_per_month: Commits per month
725 label_commits_per_month: Commits per month
726 label_commits_per_author: Commits per author
726 label_commits_per_author: Commits per author
727 label_diff: diff
727 label_diff: diff
728 label_view_diff: View differences
728 label_view_diff: View differences
729 label_diff_inline: inline
729 label_diff_inline: inline
730 label_diff_side_by_side: side by side
730 label_diff_side_by_side: side by side
731 label_options: Options
731 label_options: Options
732 label_copy_workflow_from: Copy workflow from
732 label_copy_workflow_from: Copy workflow from
733 label_permissions_report: Permissions report
733 label_permissions_report: Permissions report
734 label_watched_issues: Watched issues
734 label_watched_issues: Watched issues
735 label_related_issues: Related issues
735 label_related_issues: Related issues
736 label_applied_status: Applied status
736 label_applied_status: Applied status
737 label_loading: Loading...
737 label_loading: Loading...
738 label_relation_new: New relation
738 label_relation_new: New relation
739 label_relation_delete: Delete relation
739 label_relation_delete: Delete relation
740 label_relates_to: related to
740 label_relates_to: related to
741 label_duplicates: duplicates
741 label_duplicates: duplicates
742 label_duplicated_by: duplicated by
742 label_duplicated_by: duplicated by
743 label_blocks: blocks
743 label_blocks: blocks
744 label_blocked_by: blocked by
744 label_blocked_by: blocked by
745 label_precedes: precedes
745 label_precedes: precedes
746 label_follows: follows
746 label_follows: follows
747 label_end_to_start: end to start
747 label_end_to_start: end to start
748 label_end_to_end: end to end
748 label_end_to_end: end to end
749 label_start_to_start: start to start
749 label_start_to_start: start to start
750 label_start_to_end: start to end
750 label_start_to_end: start to end
751 label_stay_logged_in: Stay logged in
751 label_stay_logged_in: Stay logged in
752 label_disabled: disabled
752 label_disabled: disabled
753 label_show_completed_versions: Show completed versions
753 label_show_completed_versions: Show completed versions
754 label_me: me
754 label_me: me
755 label_board: Forum
755 label_board: Forum
756 label_board_new: New forum
756 label_board_new: New forum
757 label_board_plural: Forums
757 label_board_plural: Forums
758 label_board_locked: Locked
758 label_board_locked: Locked
759 label_board_sticky: Sticky
759 label_board_sticky: Sticky
760 label_topic_plural: Topics
760 label_topic_plural: Topics
761 label_message_plural: Messages
761 label_message_plural: Messages
762 label_message_last: Last message
762 label_message_last: Last message
763 label_message_new: New message
763 label_message_new: New message
764 label_message_posted: Message added
764 label_message_posted: Message added
765 label_reply_plural: Replies
765 label_reply_plural: Replies
766 label_send_information: Send account information to the user
766 label_send_information: Send account information to the user
767 label_year: Year
767 label_year: Year
768 label_month: Month
768 label_month: Month
769 label_week: Week
769 label_week: Week
770 label_date_from: From
770 label_date_from: From
771 label_date_to: To
771 label_date_to: To
772 label_language_based: Based on user's language
772 label_language_based: Based on user's language
773 label_sort_by: "Sort by %{value}"
773 label_sort_by: "Sort by %{value}"
774 label_send_test_email: Send a test email
774 label_send_test_email: Send a test email
775 label_feeds_access_key: RSS access key
775 label_feeds_access_key: RSS access key
776 label_missing_feeds_access_key: Missing a RSS access key
776 label_missing_feeds_access_key: Missing a RSS access key
777 label_feeds_access_key_created_on: "RSS access key created %{value} ago"
777 label_feeds_access_key_created_on: "RSS access key created %{value} ago"
778 label_module_plural: Modules
778 label_module_plural: Modules
779 label_added_time_by: "Added by %{author} %{age} ago"
779 label_added_time_by: "Added by %{author} %{age} ago"
780 label_updated_time_by: "Updated by %{author} %{age} ago"
780 label_updated_time_by: "Updated by %{author} %{age} ago"
781 label_updated_time: "Updated %{value} ago"
781 label_updated_time: "Updated %{value} ago"
782 label_jump_to_a_project: Jump to a project...
782 label_jump_to_a_project: Jump to a project...
783 label_file_plural: Files
783 label_file_plural: Files
784 label_changeset_plural: Changesets
784 label_changeset_plural: Changesets
785 label_default_columns: Default columns
785 label_default_columns: Default columns
786 label_no_change_option: (No change)
786 label_no_change_option: (No change)
787 label_bulk_edit_selected_issues: Bulk edit selected issues
787 label_bulk_edit_selected_issues: Bulk edit selected issues
788 label_bulk_edit_selected_time_entries: Bulk edit selected time entries
788 label_bulk_edit_selected_time_entries: Bulk edit selected time entries
789 label_theme: Theme
789 label_theme: Theme
790 label_default: Default
790 label_default: Default
791 label_search_titles_only: Search titles only
791 label_search_titles_only: Search titles only
792 label_user_mail_option_all: "For any event on all my projects"
792 label_user_mail_option_all: "For any event on all my projects"
793 label_user_mail_option_selected: "For any event on the selected projects only..."
793 label_user_mail_option_selected: "For any event on the selected projects only..."
794 label_user_mail_option_none: "No events"
794 label_user_mail_option_none: "No events"
795 label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in"
795 label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in"
796 label_user_mail_option_only_assigned: "Only for things I am assigned to"
796 label_user_mail_option_only_assigned: "Only for things I am assigned to"
797 label_user_mail_option_only_owner: "Only for things I am the owner of"
797 label_user_mail_option_only_owner: "Only for things I am the owner of"
798 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
798 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
799 label_registration_activation_by_email: account activation by email
799 label_registration_activation_by_email: account activation by email
800 label_registration_manual_activation: manual account activation
800 label_registration_manual_activation: manual account activation
801 label_registration_automatic_activation: automatic account activation
801 label_registration_automatic_activation: automatic account activation
802 label_display_per_page: "Per page: %{value}"
802 label_display_per_page: "Per page: %{value}"
803 label_age: Age
803 label_age: Age
804 label_change_properties: Change properties
804 label_change_properties: Change properties
805 label_general: General
805 label_general: General
806 label_more: More
806 label_more: More
807 label_scm: SCM
807 label_scm: SCM
808 label_plugins: Plugins
808 label_plugins: Plugins
809 label_ldap_authentication: LDAP authentication
809 label_ldap_authentication: LDAP authentication
810 label_downloads_abbr: D/L
810 label_downloads_abbr: D/L
811 label_optional_description: Optional description
811 label_optional_description: Optional description
812 label_add_another_file: Add another file
812 label_add_another_file: Add another file
813 label_preferences: Preferences
813 label_preferences: Preferences
814 label_chronological_order: In chronological order
814 label_chronological_order: In chronological order
815 label_reverse_chronological_order: In reverse chronological order
815 label_reverse_chronological_order: In reverse chronological order
816 label_planning: Planning
816 label_planning: Planning
817 label_incoming_emails: Incoming emails
817 label_incoming_emails: Incoming emails
818 label_generate_key: Generate a key
818 label_generate_key: Generate a key
819 label_issue_watchers: Watchers
819 label_issue_watchers: Watchers
820 label_example: Example
820 label_example: Example
821 label_display: Display
821 label_display: Display
822 label_sort: Sort
822 label_sort: Sort
823 label_ascending: Ascending
823 label_ascending: Ascending
824 label_descending: Descending
824 label_descending: Descending
825 label_date_from_to: From %{start} to %{end}
825 label_date_from_to: From %{start} to %{end}
826 label_wiki_content_added: Wiki page added
826 label_wiki_content_added: Wiki page added
827 label_wiki_content_updated: Wiki page updated
827 label_wiki_content_updated: Wiki page updated
828 label_group: Group
828 label_group: Group
829 label_group_plural: Groups
829 label_group_plural: Groups
830 label_group_new: New group
830 label_group_new: New group
831 label_time_entry_plural: Spent time
831 label_time_entry_plural: Spent time
832 label_version_sharing_none: Not shared
832 label_version_sharing_none: Not shared
833 label_version_sharing_descendants: With subprojects
833 label_version_sharing_descendants: With subprojects
834 label_version_sharing_hierarchy: With project hierarchy
834 label_version_sharing_hierarchy: With project hierarchy
835 label_version_sharing_tree: With project tree
835 label_version_sharing_tree: With project tree
836 label_version_sharing_system: With all projects
836 label_version_sharing_system: With all projects
837 label_update_issue_done_ratios: Update issue done ratios
837 label_update_issue_done_ratios: Update issue done ratios
838 label_copy_source: Source
838 label_copy_source: Source
839 label_copy_target: Target
839 label_copy_target: Target
840 label_copy_same_as_target: Same as target
840 label_copy_same_as_target: Same as target
841 label_display_used_statuses_only: Only display statuses that are used by this tracker
841 label_display_used_statuses_only: Only display statuses that are used by this tracker
842 label_api_access_key: API access key
842 label_api_access_key: API access key
843 label_missing_api_access_key: Missing an API access key
843 label_missing_api_access_key: Missing an API access key
844 label_api_access_key_created_on: "API access key created %{value} ago"
844 label_api_access_key_created_on: "API access key created %{value} ago"
845 label_profile: Profile
845 label_profile: Profile
846 label_subtask_plural: Subtasks
846 label_subtask_plural: Subtasks
847 label_project_copy_notifications: Send email notifications during the project copy
847 label_project_copy_notifications: Send email notifications during the project copy
848 label_principal_search: "Search for user or group:"
848 label_principal_search: "Search for user or group:"
849 label_user_search: "Search for user:"
849 label_user_search: "Search for user:"
850 label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
850 label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
851 label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
851 label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
852 label_issues_visibility_all: All issues
852 label_issues_visibility_all: All issues
853 label_issues_visibility_public: All non private issues
853 label_issues_visibility_public: All non private issues
854 label_issues_visibility_own: Issues created by or assigned to the user
854 label_issues_visibility_own: Issues created by or assigned to the user
855 label_git_report_last_commit: Report last commit for files and directories
855 label_git_report_last_commit: Report last commit for files and directories
856 label_parent_revision: Parent
856 label_parent_revision: Parent
857 label_child_revision: Child
857 label_child_revision: Child
858 label_export_options: "%{export_format} export options"
858 label_export_options: "%{export_format} export options"
859 label_copy_attachments: Copy attachments
859 label_copy_attachments: Copy attachments
860 label_item_position: "%{position} of %{count}"
860 label_item_position: "%{position} of %{count}"
861 label_completed_versions: Completed versions
861 label_completed_versions: Completed versions
862 label_search_for_watchers: Search for watchers to add
862 label_search_for_watchers: Search for watchers to add
863 label_session_expiration: Session expiration
863 label_session_expiration: Session expiration
864 label_show_closed_projects: View closed projects
864 label_show_closed_projects: View closed projects
865 label_status_transitions: Status transitions
865 label_status_transitions: Status transitions
866 label_fields_permissions: Fields permissions
866 label_fields_permissions: Fields permissions
867 label_readonly: Read-only
867 label_readonly: Read-only
868 label_required: Required
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 button_login: Login
874 button_login: Login
871 button_submit: Submit
875 button_submit: Submit
872 button_save: Save
876 button_save: Save
873 button_check_all: Check all
877 button_check_all: Check all
874 button_uncheck_all: Uncheck all
878 button_uncheck_all: Uncheck all
875 button_collapse_all: Collapse all
879 button_collapse_all: Collapse all
876 button_expand_all: Expand all
880 button_expand_all: Expand all
877 button_delete: Delete
881 button_delete: Delete
878 button_create: Create
882 button_create: Create
879 button_create_and_continue: Create and continue
883 button_create_and_continue: Create and continue
880 button_test: Test
884 button_test: Test
881 button_edit: Edit
885 button_edit: Edit
882 button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
886 button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
883 button_add: Add
887 button_add: Add
884 button_change: Change
888 button_change: Change
885 button_apply: Apply
889 button_apply: Apply
886 button_clear: Clear
890 button_clear: Clear
887 button_lock: Lock
891 button_lock: Lock
888 button_unlock: Unlock
892 button_unlock: Unlock
889 button_download: Download
893 button_download: Download
890 button_list: List
894 button_list: List
891 button_view: View
895 button_view: View
892 button_move: Move
896 button_move: Move
893 button_move_and_follow: Move and follow
897 button_move_and_follow: Move and follow
894 button_back: Back
898 button_back: Back
895 button_cancel: Cancel
899 button_cancel: Cancel
896 button_activate: Activate
900 button_activate: Activate
897 button_sort: Sort
901 button_sort: Sort
898 button_log_time: Log time
902 button_log_time: Log time
899 button_rollback: Rollback to this version
903 button_rollback: Rollback to this version
900 button_watch: Watch
904 button_watch: Watch
901 button_unwatch: Unwatch
905 button_unwatch: Unwatch
902 button_reply: Reply
906 button_reply: Reply
903 button_archive: Archive
907 button_archive: Archive
904 button_unarchive: Unarchive
908 button_unarchive: Unarchive
905 button_reset: Reset
909 button_reset: Reset
906 button_rename: Rename
910 button_rename: Rename
907 button_change_password: Change password
911 button_change_password: Change password
908 button_copy: Copy
912 button_copy: Copy
909 button_copy_and_follow: Copy and follow
913 button_copy_and_follow: Copy and follow
910 button_annotate: Annotate
914 button_annotate: Annotate
911 button_update: Update
915 button_update: Update
912 button_configure: Configure
916 button_configure: Configure
913 button_quote: Quote
917 button_quote: Quote
914 button_duplicate: Duplicate
918 button_duplicate: Duplicate
915 button_show: Show
919 button_show: Show
916 button_edit_section: Edit this section
920 button_edit_section: Edit this section
917 button_export: Export
921 button_export: Export
918 button_delete_my_account: Delete my account
922 button_delete_my_account: Delete my account
919 button_close: Close
923 button_close: Close
920 button_reopen: Reopen
924 button_reopen: Reopen
921
925
922 status_active: active
926 status_active: active
923 status_registered: registered
927 status_registered: registered
924 status_locked: locked
928 status_locked: locked
925
929
926 project_status_active: active
930 project_status_active: active
927 project_status_closed: closed
931 project_status_closed: closed
928 project_status_archived: archived
932 project_status_archived: archived
929
933
930 version_status_open: open
934 version_status_open: open
931 version_status_locked: locked
935 version_status_locked: locked
932 version_status_closed: closed
936 version_status_closed: closed
933
937
934 field_active: Active
938 field_active: Active
935
939
936 text_select_mail_notifications: Select actions for which email notifications should be sent.
940 text_select_mail_notifications: Select actions for which email notifications should be sent.
937 text_regexp_info: eg. ^[A-Z0-9]+$
941 text_regexp_info: eg. ^[A-Z0-9]+$
938 text_min_max_length_info: 0 means no restriction
942 text_min_max_length_info: 0 means no restriction
939 text_project_destroy_confirmation: Are you sure you want to delete this project and related data?
943 text_project_destroy_confirmation: Are you sure you want to delete this project and related data?
940 text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
944 text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
941 text_workflow_edit: Select a role and a tracker to edit the workflow
945 text_workflow_edit: Select a role and a tracker to edit the workflow
942 text_are_you_sure: Are you sure?
946 text_are_you_sure: Are you sure?
943 text_are_you_sure_with_children: "Delete issue and all child issues?"
947 text_are_you_sure_with_children: "Delete issue and all child issues?"
944 text_journal_changed: "%{label} changed from %{old} to %{new}"
948 text_journal_changed: "%{label} changed from %{old} to %{new}"
945 text_journal_changed_no_detail: "%{label} updated"
949 text_journal_changed_no_detail: "%{label} updated"
946 text_journal_set_to: "%{label} set to %{value}"
950 text_journal_set_to: "%{label} set to %{value}"
947 text_journal_deleted: "%{label} deleted (%{old})"
951 text_journal_deleted: "%{label} deleted (%{old})"
948 text_journal_added: "%{label} %{value} added"
952 text_journal_added: "%{label} %{value} added"
949 text_tip_issue_begin_day: issue beginning this day
953 text_tip_issue_begin_day: issue beginning this day
950 text_tip_issue_end_day: issue ending this day
954 text_tip_issue_end_day: issue ending this day
951 text_tip_issue_begin_end_day: issue beginning and ending this day
955 text_tip_issue_begin_end_day: issue beginning and ending this day
952 text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
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 text_caracters_maximum: "%{count} characters maximum."
957 text_caracters_maximum: "%{count} characters maximum."
954 text_caracters_minimum: "Must be at least %{count} characters long."
958 text_caracters_minimum: "Must be at least %{count} characters long."
955 text_length_between: "Length between %{min} and %{max} characters."
959 text_length_between: "Length between %{min} and %{max} characters."
956 text_tracker_no_workflow: No workflow defined for this tracker
960 text_tracker_no_workflow: No workflow defined for this tracker
957 text_unallowed_characters: Unallowed characters
961 text_unallowed_characters: Unallowed characters
958 text_comma_separated: Multiple values allowed (comma separated).
962 text_comma_separated: Multiple values allowed (comma separated).
959 text_line_separated: Multiple values allowed (one line for each value).
963 text_line_separated: Multiple values allowed (one line for each value).
960 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
964 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
961 text_issue_added: "Issue %{id} has been reported by %{author}."
965 text_issue_added: "Issue %{id} has been reported by %{author}."
962 text_issue_updated: "Issue %{id} has been updated by %{author}."
966 text_issue_updated: "Issue %{id} has been updated by %{author}."
963 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content?
967 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content?
964 text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?"
968 text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?"
965 text_issue_category_destroy_assignments: Remove category assignments
969 text_issue_category_destroy_assignments: Remove category assignments
966 text_issue_category_reassign_to: Reassign issues to this category
970 text_issue_category_reassign_to: Reassign issues to this category
967 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)."
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 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."
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 text_load_default_configuration: Load the default configuration
973 text_load_default_configuration: Load the default configuration
970 text_status_changed_by_changeset: "Applied in changeset %{value}."
974 text_status_changed_by_changeset: "Applied in changeset %{value}."
971 text_time_logged_by_changeset: "Applied in changeset %{value}."
975 text_time_logged_by_changeset: "Applied in changeset %{value}."
972 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
976 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
973 text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)."
977 text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)."
974 text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?'
978 text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?'
975 text_select_project_modules: 'Select modules to enable for this project:'
979 text_select_project_modules: 'Select modules to enable for this project:'
976 text_default_administrator_account_changed: Default administrator account changed
980 text_default_administrator_account_changed: Default administrator account changed
977 text_file_repository_writable: Attachments directory writable
981 text_file_repository_writable: Attachments directory writable
978 text_plugin_assets_writable: Plugin assets directory writable
982 text_plugin_assets_writable: Plugin assets directory writable
979 text_rmagick_available: RMagick available (optional)
983 text_rmagick_available: RMagick available (optional)
980 text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?"
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 text_destroy_time_entries: Delete reported hours
985 text_destroy_time_entries: Delete reported hours
982 text_assign_time_entries_to_project: Assign reported hours to the project
986 text_assign_time_entries_to_project: Assign reported hours to the project
983 text_reassign_time_entries: 'Reassign reported hours to this issue:'
987 text_reassign_time_entries: 'Reassign reported hours to this issue:'
984 text_user_wrote: "%{value} wrote:"
988 text_user_wrote: "%{value} wrote:"
985 text_enumeration_destroy_question: "%{count} objects are assigned to this value."
989 text_enumeration_destroy_question: "%{count} objects are assigned to this value."
986 text_enumeration_category_reassign_to: 'Reassign them to this value:'
990 text_enumeration_category_reassign_to: 'Reassign them to this value:'
987 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."
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 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."
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 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
993 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
990 text_custom_field_possible_values_info: 'One line for each value'
994 text_custom_field_possible_values_info: 'One line for each value'
991 text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?"
995 text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?"
992 text_wiki_page_nullify_children: "Keep child pages as root pages"
996 text_wiki_page_nullify_children: "Keep child pages as root pages"
993 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
997 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
994 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
998 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
995 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?"
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 text_zoom_in: Zoom in
1000 text_zoom_in: Zoom in
997 text_zoom_out: Zoom out
1001 text_zoom_out: Zoom out
998 text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page."
1002 text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page."
999 text_scm_path_encoding_note: "Default: UTF-8"
1003 text_scm_path_encoding_note: "Default: UTF-8"
1000 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
1004 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
1001 text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo)
1005 text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo)
1002 text_scm_command: Command
1006 text_scm_command: Command
1003 text_scm_command_version: Version
1007 text_scm_command_version: Version
1004 text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it.
1008 text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it.
1005 text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
1009 text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
1006 text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)"
1010 text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)"
1007 text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes"
1011 text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes"
1008 text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}"
1012 text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}"
1009 text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it."
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 text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours."
1014 text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours."
1011 text_project_closed: This project is closed and read-only.
1015 text_project_closed: This project is closed and read-only.
1012
1016
1013 default_role_manager: Manager
1017 default_role_manager: Manager
1014 default_role_developer: Developer
1018 default_role_developer: Developer
1015 default_role_reporter: Reporter
1019 default_role_reporter: Reporter
1016 default_tracker_bug: Bug
1020 default_tracker_bug: Bug
1017 default_tracker_feature: Feature
1021 default_tracker_feature: Feature
1018 default_tracker_support: Support
1022 default_tracker_support: Support
1019 default_issue_status_new: New
1023 default_issue_status_new: New
1020 default_issue_status_in_progress: In Progress
1024 default_issue_status_in_progress: In Progress
1021 default_issue_status_resolved: Resolved
1025 default_issue_status_resolved: Resolved
1022 default_issue_status_feedback: Feedback
1026 default_issue_status_feedback: Feedback
1023 default_issue_status_closed: Closed
1027 default_issue_status_closed: Closed
1024 default_issue_status_rejected: Rejected
1028 default_issue_status_rejected: Rejected
1025 default_doc_category_user: User documentation
1029 default_doc_category_user: User documentation
1026 default_doc_category_tech: Technical documentation
1030 default_doc_category_tech: Technical documentation
1027 default_priority_low: Low
1031 default_priority_low: Low
1028 default_priority_normal: Normal
1032 default_priority_normal: Normal
1029 default_priority_high: High
1033 default_priority_high: High
1030 default_priority_urgent: Urgent
1034 default_priority_urgent: Urgent
1031 default_priority_immediate: Immediate
1035 default_priority_immediate: Immediate
1032 default_activity_design: Design
1036 default_activity_design: Design
1033 default_activity_development: Development
1037 default_activity_development: Development
1034
1038
1035 enumeration_issue_priorities: Issue priorities
1039 enumeration_issue_priorities: Issue priorities
1036 enumeration_doc_categories: Document categories
1040 enumeration_doc_categories: Document categories
1037 enumeration_activities: Activities (time tracking)
1041 enumeration_activities: Activities (time tracking)
1038 enumeration_system_activity: System Activity
1042 enumeration_system_activity: System Activity
1039 description_filter: Filter
1043 description_filter: Filter
1040 description_search: Searchfield
1044 description_search: Searchfield
1041 description_choose_project: Projects
1045 description_choose_project: Projects
1042 description_project_scope: Search scope
1046 description_project_scope: Search scope
1043 description_notes: Notes
1047 description_notes: Notes
1044 description_message_content: Message content
1048 description_message_content: Message content
1045 description_query_sort_criteria_attribute: Sort attribute
1049 description_query_sort_criteria_attribute: Sort attribute
1046 description_query_sort_criteria_direction: Sort direction
1050 description_query_sort_criteria_direction: Sort direction
1047 description_user_mail_notification: Mail notification settings
1051 description_user_mail_notification: Mail notification settings
1048 description_available_columns: Available Columns
1052 description_available_columns: Available Columns
1049 description_selected_columns: Selected Columns
1053 description_selected_columns: Selected Columns
1050 description_all_columns: All Columns
1054 description_all_columns: All Columns
1051 description_issue_category_reassign: Choose issue category
1055 description_issue_category_reassign: Choose issue category
1052 description_wiki_subpages_reassign: Choose new parent page
1056 description_wiki_subpages_reassign: Choose new parent page
1053 description_date_range_list: Choose range from list
1057 description_date_range_list: Choose range from list
1054 description_date_range_interval: Choose range by selecting start and end date
1058 description_date_range_interval: Choose range by selecting start and end date
1055 description_date_from: Enter start date
1059 description_date_from: Enter start date
1056 description_date_to: Enter end date
1060 description_date_to: Enter end date
1057 text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
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 # French translations for Ruby on Rails
1 # French translations for Ruby on Rails
2 # by Christian Lescuyer (christian@flyingcoders.com)
2 # by Christian Lescuyer (christian@flyingcoders.com)
3 # contributor: Sebastien Grosjean - ZenCocoon.com
3 # contributor: Sebastien Grosjean - ZenCocoon.com
4 # contributor: Thibaut Cuvelier - Developpez.com
4 # contributor: Thibaut Cuvelier - Developpez.com
5
5
6 fr:
6 fr:
7 direction: ltr
7 direction: ltr
8 date:
8 date:
9 formats:
9 formats:
10 default: "%d/%m/%Y"
10 default: "%d/%m/%Y"
11 short: "%e %b"
11 short: "%e %b"
12 long: "%e %B %Y"
12 long: "%e %B %Y"
13 long_ordinal: "%e %B %Y"
13 long_ordinal: "%e %B %Y"
14 only_day: "%e"
14 only_day: "%e"
15
15
16 day_names: [dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi]
16 day_names: [dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi]
17 abbr_day_names: [dim, lun, mar, mer, jeu, ven, sam]
17 abbr_day_names: [dim, lun, mar, mer, jeu, ven, sam]
18 month_names: [~, janvier, fΓ©vrier, mars, avril, mai, juin, juillet, aoΓ»t, septembre, octobre, novembre, dΓ©cembre]
18 month_names: [~, janvier, fΓ©vrier, mars, avril, mai, juin, juillet, aoΓ»t, septembre, octobre, novembre, dΓ©cembre]
19 abbr_month_names: [~, jan., fΓ©v., mar., avr., mai, juin, juil., aoΓ»t, sept., oct., nov., dΓ©c.]
19 abbr_month_names: [~, jan., fΓ©v., mar., avr., mai, juin, juil., aoΓ»t, sept., oct., nov., dΓ©c.]
20 order:
20 order:
21 - :day
21 - :day
22 - :month
22 - :month
23 - :year
23 - :year
24
24
25 time:
25 time:
26 formats:
26 formats:
27 default: "%d/%m/%Y %H:%M"
27 default: "%d/%m/%Y %H:%M"
28 time: "%H:%M"
28 time: "%H:%M"
29 short: "%d %b %H:%M"
29 short: "%d %b %H:%M"
30 long: "%A %d %B %Y %H:%M:%S %Z"
30 long: "%A %d %B %Y %H:%M:%S %Z"
31 long_ordinal: "%A %d %B %Y %H:%M:%S %Z"
31 long_ordinal: "%A %d %B %Y %H:%M:%S %Z"
32 only_second: "%S"
32 only_second: "%S"
33 am: 'am'
33 am: 'am'
34 pm: 'pm'
34 pm: 'pm'
35
35
36 datetime:
36 datetime:
37 distance_in_words:
37 distance_in_words:
38 half_a_minute: "30 secondes"
38 half_a_minute: "30 secondes"
39 less_than_x_seconds:
39 less_than_x_seconds:
40 zero: "moins d'une seconde"
40 zero: "moins d'une seconde"
41 one: "moins d'uneΒ seconde"
41 one: "moins d'uneΒ seconde"
42 other: "moins de %{count}Β secondes"
42 other: "moins de %{count}Β secondes"
43 x_seconds:
43 x_seconds:
44 one: "1Β seconde"
44 one: "1Β seconde"
45 other: "%{count}Β secondes"
45 other: "%{count}Β secondes"
46 less_than_x_minutes:
46 less_than_x_minutes:
47 zero: "moins d'une minute"
47 zero: "moins d'une minute"
48 one: "moins d'uneΒ minute"
48 one: "moins d'uneΒ minute"
49 other: "moins de %{count}Β minutes"
49 other: "moins de %{count}Β minutes"
50 x_minutes:
50 x_minutes:
51 one: "1Β minute"
51 one: "1Β minute"
52 other: "%{count}Β minutes"
52 other: "%{count}Β minutes"
53 about_x_hours:
53 about_x_hours:
54 one: "environ une heure"
54 one: "environ une heure"
55 other: "environ %{count}Β heures"
55 other: "environ %{count}Β heures"
56 x_hours:
56 x_hours:
57 one: "une heure"
57 one: "une heure"
58 other: "%{count}Β heures"
58 other: "%{count}Β heures"
59 x_days:
59 x_days:
60 one: "unΒ jour"
60 one: "unΒ jour"
61 other: "%{count}Β jours"
61 other: "%{count}Β jours"
62 about_x_months:
62 about_x_months:
63 one: "environ un mois"
63 one: "environ un mois"
64 other: "environ %{count}Β mois"
64 other: "environ %{count}Β mois"
65 x_months:
65 x_months:
66 one: "unΒ mois"
66 one: "unΒ mois"
67 other: "%{count}Β mois"
67 other: "%{count}Β mois"
68 about_x_years:
68 about_x_years:
69 one: "environ un an"
69 one: "environ un an"
70 other: "environ %{count}Β ans"
70 other: "environ %{count}Β ans"
71 over_x_years:
71 over_x_years:
72 one: "plus d'un an"
72 one: "plus d'un an"
73 other: "plus de %{count}Β ans"
73 other: "plus de %{count}Β ans"
74 almost_x_years:
74 almost_x_years:
75 one: "presqu'un an"
75 one: "presqu'un an"
76 other: "presque %{count} ans"
76 other: "presque %{count} ans"
77 prompts:
77 prompts:
78 year: "AnnΓ©e"
78 year: "AnnΓ©e"
79 month: "Mois"
79 month: "Mois"
80 day: "Jour"
80 day: "Jour"
81 hour: "Heure"
81 hour: "Heure"
82 minute: "Minute"
82 minute: "Minute"
83 second: "Seconde"
83 second: "Seconde"
84
84
85 number:
85 number:
86 format:
86 format:
87 precision: 3
87 precision: 3
88 separator: ','
88 separator: ','
89 delimiter: 'Β '
89 delimiter: 'Β '
90 currency:
90 currency:
91 format:
91 format:
92 unit: '€'
92 unit: '€'
93 precision: 2
93 precision: 2
94 format: '%nΒ %u'
94 format: '%nΒ %u'
95 human:
95 human:
96 format:
96 format:
97 precision: 3
97 precision: 3
98 storage_units:
98 storage_units:
99 format: "%n %u"
99 format: "%n %u"
100 units:
100 units:
101 byte:
101 byte:
102 one: "octet"
102 one: "octet"
103 other: "octet"
103 other: "octet"
104 kb: "ko"
104 kb: "ko"
105 mb: "Mo"
105 mb: "Mo"
106 gb: "Go"
106 gb: "Go"
107 tb: "To"
107 tb: "To"
108
108
109 support:
109 support:
110 array:
110 array:
111 sentence_connector: 'et'
111 sentence_connector: 'et'
112 skip_last_comma: true
112 skip_last_comma: true
113 word_connector: ", "
113 word_connector: ", "
114 two_words_connector: " et "
114 two_words_connector: " et "
115 last_word_connector: " et "
115 last_word_connector: " et "
116
116
117 activerecord:
117 activerecord:
118 errors:
118 errors:
119 template:
119 template:
120 header:
120 header:
121 one: "Impossible d'enregistrer %{model} : une erreur"
121 one: "Impossible d'enregistrer %{model} : une erreur"
122 other: "Impossible d'enregistrer %{model} : %{count} erreurs."
122 other: "Impossible d'enregistrer %{model} : %{count} erreurs."
123 body: "Veuillez vΓ©rifier les champs suivantsΒ :"
123 body: "Veuillez vΓ©rifier les champs suivantsΒ :"
124 messages:
124 messages:
125 inclusion: "n'est pas inclus(e) dans la liste"
125 inclusion: "n'est pas inclus(e) dans la liste"
126 exclusion: "n'est pas disponible"
126 exclusion: "n'est pas disponible"
127 invalid: "n'est pas valide"
127 invalid: "n'est pas valide"
128 confirmation: "ne concorde pas avec la confirmation"
128 confirmation: "ne concorde pas avec la confirmation"
129 accepted: "doit Γͺtre acceptΓ©(e)"
129 accepted: "doit Γͺtre acceptΓ©(e)"
130 empty: "doit Γͺtre renseignΓ©(e)"
130 empty: "doit Γͺtre renseignΓ©(e)"
131 blank: "doit Γͺtre renseignΓ©(e)"
131 blank: "doit Γͺtre renseignΓ©(e)"
132 too_long: "est trop long (pas plus de %{count} caractères)"
132 too_long: "est trop long (pas plus de %{count} caractères)"
133 too_short: "est trop court (au moins %{count} caractères)"
133 too_short: "est trop court (au moins %{count} caractères)"
134 wrong_length: "ne fait pas la bonne longueur (doit comporter %{count} caractères)"
134 wrong_length: "ne fait pas la bonne longueur (doit comporter %{count} caractères)"
135 taken: "est dΓ©jΓ  utilisΓ©"
135 taken: "est dΓ©jΓ  utilisΓ©"
136 not_a_number: "n'est pas un nombre"
136 not_a_number: "n'est pas un nombre"
137 not_a_date: "n'est pas une date valide"
137 not_a_date: "n'est pas une date valide"
138 greater_than: "doit Γͺtre supΓ©rieur Γ  %{count}"
138 greater_than: "doit Γͺtre supΓ©rieur Γ  %{count}"
139 greater_than_or_equal_to: "doit Γͺtre supΓ©rieur ou Γ©gal Γ  %{count}"
139 greater_than_or_equal_to: "doit Γͺtre supΓ©rieur ou Γ©gal Γ  %{count}"
140 equal_to: "doit Γͺtre Γ©gal Γ  %{count}"
140 equal_to: "doit Γͺtre Γ©gal Γ  %{count}"
141 less_than: "doit Γͺtre infΓ©rieur Γ  %{count}"
141 less_than: "doit Γͺtre infΓ©rieur Γ  %{count}"
142 less_than_or_equal_to: "doit Γͺtre infΓ©rieur ou Γ©gal Γ  %{count}"
142 less_than_or_equal_to: "doit Γͺtre infΓ©rieur ou Γ©gal Γ  %{count}"
143 odd: "doit Γͺtre impair"
143 odd: "doit Γͺtre impair"
144 even: "doit Γͺtre pair"
144 even: "doit Γͺtre pair"
145 greater_than_start_date: "doit Γͺtre postΓ©rieure Γ  la date de dΓ©but"
145 greater_than_start_date: "doit Γͺtre postΓ©rieure Γ  la date de dΓ©but"
146 not_same_project: "n'appartient pas au mΓͺme projet"
146 not_same_project: "n'appartient pas au mΓͺme projet"
147 circular_dependency: "Cette relation crΓ©erait une dΓ©pendance circulaire"
147 circular_dependency: "Cette relation crΓ©erait une dΓ©pendance circulaire"
148 cant_link_an_issue_with_a_descendant: "Une demande ne peut pas Γͺtre liΓ©e Γ  l'une de ses sous-tΓ’ches"
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 actionview_instancetag_blank_option: Choisir
150 actionview_instancetag_blank_option: Choisir
151
151
152 general_text_No: 'Non'
152 general_text_No: 'Non'
153 general_text_Yes: 'Oui'
153 general_text_Yes: 'Oui'
154 general_text_no: 'non'
154 general_text_no: 'non'
155 general_text_yes: 'oui'
155 general_text_yes: 'oui'
156 general_lang_name: 'FranΓ§ais'
156 general_lang_name: 'FranΓ§ais'
157 general_csv_separator: ';'
157 general_csv_separator: ';'
158 general_csv_decimal_separator: ','
158 general_csv_decimal_separator: ','
159 general_csv_encoding: ISO-8859-1
159 general_csv_encoding: ISO-8859-1
160 general_pdf_encoding: UTF-8
160 general_pdf_encoding: UTF-8
161 general_first_day_of_week: '1'
161 general_first_day_of_week: '1'
162
162
163 notice_account_updated: Le compte a été mis à jour avec succès.
163 notice_account_updated: Le compte a été mis à jour avec succès.
164 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
164 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
165 notice_account_password_updated: Mot de passe mis à jour avec succès.
165 notice_account_password_updated: Mot de passe mis à jour avec succès.
166 notice_account_wrong_password: Mot de passe incorrect
166 notice_account_wrong_password: Mot de passe incorrect
167 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a Γ©tΓ© envoyΓ©.
167 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a Γ©tΓ© envoyΓ©.
168 notice_account_unknown_email: Aucun compte ne correspond Γ  cette adresse.
168 notice_account_unknown_email: Aucun compte ne correspond Γ  cette adresse.
169 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
169 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
170 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a Γ©tΓ© envoyΓ©.
170 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a Γ©tΓ© envoyΓ©.
171 notice_account_activated: Votre compte a Γ©tΓ© activΓ©. Vous pouvez Γ  prΓ©sent vous connecter.
171 notice_account_activated: Votre compte a Γ©tΓ© activΓ©. Vous pouvez Γ  prΓ©sent vous connecter.
172 notice_successful_create: Création effectuée avec succès.
172 notice_successful_create: Création effectuée avec succès.
173 notice_successful_update: Mise à jour effectuée avec succès.
173 notice_successful_update: Mise à jour effectuée avec succès.
174 notice_successful_delete: Suppression effectuée avec succès.
174 notice_successful_delete: Suppression effectuée avec succès.
175 notice_successful_connection: Connexion rΓ©ussie.
175 notice_successful_connection: Connexion rΓ©ussie.
176 notice_file_not_found: "La page Γ  laquelle vous souhaitez accΓ©der n'existe pas ou a Γ©tΓ© supprimΓ©e."
176 notice_file_not_found: "La page Γ  laquelle vous souhaitez accΓ©der n'existe pas ou a Γ©tΓ© supprimΓ©e."
177 notice_locking_conflict: Les donnΓ©es ont Γ©tΓ© mises Γ  jour par un autre utilisateur. Mise Γ  jour impossible.
177 notice_locking_conflict: Les donnΓ©es ont Γ©tΓ© mises Γ  jour par un autre utilisateur. Mise Γ  jour impossible.
178 notice_not_authorized: "Vous n'Γͺtes pas autorisΓ© Γ  accΓ©der Γ  cette page."
178 notice_not_authorized: "Vous n'Γͺtes pas autorisΓ© Γ  accΓ©der Γ  cette page."
179 notice_not_authorized_archived_project: Le projet auquel vous tentez d'accΓ©der a Γ©tΓ© archivΓ©.
179 notice_not_authorized_archived_project: Le projet auquel vous tentez d'accΓ©der a Γ©tΓ© archivΓ©.
180 notice_email_sent: "Un email a Γ©tΓ© envoyΓ© Γ  %{value}"
180 notice_email_sent: "Un email a Γ©tΓ© envoyΓ© Γ  %{value}"
181 notice_email_error: "Erreur lors de l'envoi de l'email (%{value})"
181 notice_email_error: "Erreur lors de l'envoi de l'email (%{value})"
182 notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée."
182 notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée."
183 notice_failed_to_save_issues: "%{count} demande(s) sur les %{total} sΓ©lectionnΓ©es n'ont pas pu Γͺtre mise(s) Γ  jour : %{ids}."
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 notice_failed_to_save_time_entries: "%{count} temps passΓ©(s) sur les %{total} sΓ©lectionnΓ©s n'ont pas pu Γͺtre mis Γ  jour: %{ids}."
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 notice_no_issue_selected: "Aucune demande sΓ©lectionnΓ©e ! Cochez les demandes que vous voulez mettre Γ  jour."
185 notice_no_issue_selected: "Aucune demande sΓ©lectionnΓ©e ! Cochez les demandes que vous voulez mettre Γ  jour."
186 notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur."
186 notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur."
187 notice_default_data_loaded: Paramétrage par défaut chargé avec succès.
187 notice_default_data_loaded: Paramétrage par défaut chargé avec succès.
188 notice_unable_delete_version: Impossible de supprimer cette version.
188 notice_unable_delete_version: Impossible de supprimer cette version.
189 notice_issue_done_ratios_updated: L'avancement des demandes a Γ©tΓ© mis Γ  jour.
189 notice_issue_done_ratios_updated: L'avancement des demandes a Γ©tΓ© mis Γ  jour.
190 notice_api_access_key_reseted: Votre clé d'accès API a été réinitialisée.
190 notice_api_access_key_reseted: Votre clé d'accès API a été réinitialisée.
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})"
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 notice_issue_successful_create: "Demande %{id} créée."
192 notice_issue_successful_create: "Demande %{id} créée."
193 notice_issue_update_conflict: "La demande a Γ©tΓ© mise Γ  jour par un autre utilisateur pendant que vous la modifiez."
193 notice_issue_update_conflict: "La demande a Γ©tΓ© mise Γ  jour par un autre utilisateur pendant que vous la modifiez."
194 notice_account_deleted: "Votre compte a Γ©tΓ© dΓ©finitivement supprimΓ©."
194 notice_account_deleted: "Votre compte a Γ©tΓ© dΓ©finitivement supprimΓ©."
195 notice_user_successful_create: "Utilisateur %{id} créé."
195 notice_user_successful_create: "Utilisateur %{id} créé."
196
196
197 error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramΓ©trage : %{value}"
197 error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramΓ©trage : %{value}"
198 error_scm_not_found: "L'entrΓ©e et/ou la rΓ©vision demandΓ©e n'existe pas dans le dΓ©pΓ΄t."
198 error_scm_not_found: "L'entrΓ©e et/ou la rΓ©vision demandΓ©e n'existe pas dans le dΓ©pΓ΄t."
199 error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt : %{value}"
199 error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt : %{value}"
200 error_scm_annotate: "L'entrΓ©e n'existe pas ou ne peut pas Γͺtre annotΓ©e."
200 error_scm_annotate: "L'entrΓ©e n'existe pas ou ne peut pas Γͺtre annotΓ©e."
201 error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ  ce projet"
201 error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ  ce projet"
202 error_can_not_reopen_issue_on_closed_version: 'Une demande assignΓ©e Γ  une version fermΓ©e ne peut pas Γͺtre rΓ©ouverte'
202 error_can_not_reopen_issue_on_closed_version: 'Une demande assignΓ©e Γ  une version fermΓ©e ne peut pas Γͺtre rΓ©ouverte'
203 error_can_not_archive_project: "Ce projet ne peut pas Γͺtre archivΓ©"
203 error_can_not_archive_project: "Ce projet ne peut pas Γͺtre archivΓ©"
204 error_workflow_copy_source: 'Veuillez sΓ©lectionner un tracker et/ou un rΓ΄le source'
204 error_workflow_copy_source: 'Veuillez sΓ©lectionner un tracker et/ou un rΓ΄le source'
205 error_workflow_copy_target: 'Veuillez sΓ©lectionner les trackers et rΓ΄les cibles'
205 error_workflow_copy_target: 'Veuillez sΓ©lectionner les trackers et rΓ΄les cibles'
206 error_issue_done_ratios_not_updated: L'avancement des demandes n'a pas pu Γͺtre mis Γ  jour.
206 error_issue_done_ratios_not_updated: L'avancement des demandes n'a pas pu Γͺtre mis Γ  jour.
207 error_attachment_too_big: Ce fichier ne peut pas Γͺtre attachΓ© car il excΓ¨de la taille maximale autorisΓ©e (%{max_size})
207 error_attachment_too_big: Ce fichier ne peut pas Γͺtre attachΓ© car il excΓ¨de la taille maximale autorisΓ©e (%{max_size})
208 error_session_expired: "Votre session a expirΓ©. Veuillez vous reconnecter."
208 error_session_expired: "Votre session a expirΓ©. Veuillez vous reconnecter."
209
209
210 warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu Γͺtre sauvegardΓ©s."
210 warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu Γͺtre sauvegardΓ©s."
211
211
212 mail_subject_lost_password: "Votre mot de passe %{value}"
212 mail_subject_lost_password: "Votre mot de passe %{value}"
213 mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant :'
213 mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant :'
214 mail_subject_register: "Activation de votre compte %{value}"
214 mail_subject_register: "Activation de votre compte %{value}"
215 mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant :'
215 mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant :'
216 mail_body_account_information_external: "Vous pouvez utiliser votre compte %{value} pour vous connecter."
216 mail_body_account_information_external: "Vous pouvez utiliser votre compte %{value} pour vous connecter."
217 mail_body_account_information: Paramètres de connexion de votre compte
217 mail_body_account_information: Paramètres de connexion de votre compte
218 mail_subject_account_activation_request: "Demande d'activation d'un compte %{value}"
218 mail_subject_account_activation_request: "Demande d'activation d'un compte %{value}"
219 mail_body_account_activation_request: "Un nouvel utilisateur (%{value}) s'est inscrit. Son compte nΓ©cessite votre approbation :"
219 mail_body_account_activation_request: "Un nouvel utilisateur (%{value}) s'est inscrit. Son compte nΓ©cessite votre approbation :"
220 mail_subject_reminder: "%{count} demande(s) arrivent Γ  Γ©chΓ©ance (%{days})"
220 mail_subject_reminder: "%{count} demande(s) arrivent Γ  Γ©chΓ©ance (%{days})"
221 mail_body_reminder: "%{count} demande(s) qui vous sont assignΓ©es arrivent Γ  Γ©chΓ©ance dans les %{days} prochains jours :"
221 mail_body_reminder: "%{count} demande(s) qui vous sont assignΓ©es arrivent Γ  Γ©chΓ©ance dans les %{days} prochains jours :"
222 mail_subject_wiki_content_added: "Page wiki '%{id}' ajoutΓ©e"
222 mail_subject_wiki_content_added: "Page wiki '%{id}' ajoutΓ©e"
223 mail_body_wiki_content_added: "La page wiki '%{id}' a Γ©tΓ© ajoutΓ©e par %{author}."
223 mail_body_wiki_content_added: "La page wiki '%{id}' a Γ©tΓ© ajoutΓ©e par %{author}."
224 mail_subject_wiki_content_updated: "Page wiki '%{id}' mise Γ  jour"
224 mail_subject_wiki_content_updated: "Page wiki '%{id}' mise Γ  jour"
225 mail_body_wiki_content_updated: "La page wiki '%{id}' a Γ©tΓ© mise Γ  jour par %{author}."
225 mail_body_wiki_content_updated: "La page wiki '%{id}' a Γ©tΓ© mise Γ  jour par %{author}."
226
226
227 gui_validation_error: 1 erreur
227 gui_validation_error: 1 erreur
228 gui_validation_error_plural: "%{count} erreurs"
228 gui_validation_error_plural: "%{count} erreurs"
229
229
230 field_name: Nom
230 field_name: Nom
231 field_description: Description
231 field_description: Description
232 field_summary: RΓ©sumΓ©
232 field_summary: RΓ©sumΓ©
233 field_is_required: Obligatoire
233 field_is_required: Obligatoire
234 field_firstname: PrΓ©nom
234 field_firstname: PrΓ©nom
235 field_lastname: Nom
235 field_lastname: Nom
236 field_mail: "Email "
236 field_mail: "Email "
237 field_filename: Fichier
237 field_filename: Fichier
238 field_filesize: Taille
238 field_filesize: Taille
239 field_downloads: TΓ©lΓ©chargements
239 field_downloads: TΓ©lΓ©chargements
240 field_author: Auteur
240 field_author: Auteur
241 field_created_on: "Créé "
241 field_created_on: "Créé "
242 field_updated_on: "Mis-Γ -jour "
242 field_updated_on: "Mis-Γ -jour "
243 field_field_format: Format
243 field_field_format: Format
244 field_is_for_all: Pour tous les projets
244 field_is_for_all: Pour tous les projets
245 field_possible_values: Valeurs possibles
245 field_possible_values: Valeurs possibles
246 field_regexp: Expression régulière
246 field_regexp: Expression régulière
247 field_min_length: Longueur minimum
247 field_min_length: Longueur minimum
248 field_max_length: Longueur maximum
248 field_max_length: Longueur maximum
249 field_value: Valeur
249 field_value: Valeur
250 field_category: CatΓ©gorie
250 field_category: CatΓ©gorie
251 field_title: Titre
251 field_title: Titre
252 field_project: Projet
252 field_project: Projet
253 field_issue: Demande
253 field_issue: Demande
254 field_status: Statut
254 field_status: Statut
255 field_notes: Notes
255 field_notes: Notes
256 field_is_closed: Demande fermΓ©e
256 field_is_closed: Demande fermΓ©e
257 field_is_default: Valeur par dΓ©faut
257 field_is_default: Valeur par dΓ©faut
258 field_tracker: Tracker
258 field_tracker: Tracker
259 field_subject: Sujet
259 field_subject: Sujet
260 field_due_date: EchΓ©ance
260 field_due_date: EchΓ©ance
261 field_assigned_to: AssignΓ© Γ 
261 field_assigned_to: AssignΓ© Γ 
262 field_priority: PrioritΓ©
262 field_priority: PrioritΓ©
263 field_fixed_version: Version cible
263 field_fixed_version: Version cible
264 field_user: Utilisateur
264 field_user: Utilisateur
265 field_role: RΓ΄le
265 field_role: RΓ΄le
266 field_homepage: "Site web "
266 field_homepage: "Site web "
267 field_is_public: Public
267 field_is_public: Public
268 field_parent: Sous-projet de
268 field_parent: Sous-projet de
269 field_is_in_roadmap: Demandes affichΓ©es dans la roadmap
269 field_is_in_roadmap: Demandes affichΓ©es dans la roadmap
270 field_login: "Identifiant "
270 field_login: "Identifiant "
271 field_mail_notification: Notifications par mail
271 field_mail_notification: Notifications par mail
272 field_admin: Administrateur
272 field_admin: Administrateur
273 field_last_login_on: "Dernière connexion "
273 field_last_login_on: "Dernière connexion "
274 field_language: Langue
274 field_language: Langue
275 field_effective_date: Date
275 field_effective_date: Date
276 field_password: Mot de passe
276 field_password: Mot de passe
277 field_new_password: Nouveau mot de passe
277 field_new_password: Nouveau mot de passe
278 field_password_confirmation: Confirmation
278 field_password_confirmation: Confirmation
279 field_version: Version
279 field_version: Version
280 field_type: Type
280 field_type: Type
281 field_host: HΓ΄te
281 field_host: HΓ΄te
282 field_port: Port
282 field_port: Port
283 field_account: Compte
283 field_account: Compte
284 field_base_dn: Base DN
284 field_base_dn: Base DN
285 field_attr_login: Attribut Identifiant
285 field_attr_login: Attribut Identifiant
286 field_attr_firstname: Attribut PrΓ©nom
286 field_attr_firstname: Attribut PrΓ©nom
287 field_attr_lastname: Attribut Nom
287 field_attr_lastname: Attribut Nom
288 field_attr_mail: Attribut Email
288 field_attr_mail: Attribut Email
289 field_onthefly: CrΓ©ation des utilisateurs Γ  la volΓ©e
289 field_onthefly: CrΓ©ation des utilisateurs Γ  la volΓ©e
290 field_start_date: DΓ©but
290 field_start_date: DΓ©but
291 field_done_ratio: "% rΓ©alisΓ©"
291 field_done_ratio: "% rΓ©alisΓ©"
292 field_auth_source: Mode d'authentification
292 field_auth_source: Mode d'authentification
293 field_hide_mail: Cacher mon adresse mail
293 field_hide_mail: Cacher mon adresse mail
294 field_comments: Commentaire
294 field_comments: Commentaire
295 field_url: URL
295 field_url: URL
296 field_start_page: Page de dΓ©marrage
296 field_start_page: Page de dΓ©marrage
297 field_subproject: Sous-projet
297 field_subproject: Sous-projet
298 field_hours: Heures
298 field_hours: Heures
299 field_activity: ActivitΓ©
299 field_activity: ActivitΓ©
300 field_spent_on: Date
300 field_spent_on: Date
301 field_identifier: Identifiant
301 field_identifier: Identifiant
302 field_is_filter: UtilisΓ© comme filtre
302 field_is_filter: UtilisΓ© comme filtre
303 field_issue_to: Demande liΓ©e
303 field_issue_to: Demande liΓ©e
304 field_delay: Retard
304 field_delay: Retard
305 field_assignable: Demandes assignables Γ  ce rΓ΄le
305 field_assignable: Demandes assignables Γ  ce rΓ΄le
306 field_redirect_existing_links: Rediriger les liens existants
306 field_redirect_existing_links: Rediriger les liens existants
307 field_estimated_hours: Temps estimΓ©
307 field_estimated_hours: Temps estimΓ©
308 field_column_names: Colonnes
308 field_column_names: Colonnes
309 field_time_zone: Fuseau horaire
309 field_time_zone: Fuseau horaire
310 field_searchable: UtilisΓ© pour les recherches
310 field_searchable: UtilisΓ© pour les recherches
311 field_default_value: Valeur par dΓ©faut
311 field_default_value: Valeur par dΓ©faut
312 field_comments_sorting: Afficher les commentaires
312 field_comments_sorting: Afficher les commentaires
313 field_parent_title: Page parent
313 field_parent_title: Page parent
314 field_editable: Modifiable
314 field_editable: Modifiable
315 field_watcher: Observateur
315 field_watcher: Observateur
316 field_identity_url: URL OpenID
316 field_identity_url: URL OpenID
317 field_content: Contenu
317 field_content: Contenu
318 field_group_by: Grouper par
318 field_group_by: Grouper par
319 field_sharing: Partage
319 field_sharing: Partage
320 field_active: Actif
320 field_active: Actif
321 field_parent_issue: TΓ’che parente
321 field_parent_issue: TΓ’che parente
322 field_visible: Visible
322 field_visible: Visible
323 field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardΓ©"
323 field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardΓ©"
324 field_issues_visibility: VisibilitΓ© des demandes
324 field_issues_visibility: VisibilitΓ© des demandes
325 field_is_private: PrivΓ©e
325 field_is_private: PrivΓ©e
326 field_commit_logs_encoding: Encodage des messages de commit
326 field_commit_logs_encoding: Encodage des messages de commit
327 field_repository_is_default: DΓ©pΓ΄t principal
327 field_repository_is_default: DΓ©pΓ΄t principal
328 field_multiple: Valeurs multiples
328 field_multiple: Valeurs multiples
329 field_auth_source_ldap_filter: Filtre LDAP
329 field_auth_source_ldap_filter: Filtre LDAP
330 field_core_fields: Champs standards
330 field_core_fields: Champs standards
331 field_timeout: "Timeout (en secondes)"
331 field_timeout: "Timeout (en secondes)"
332 field_board_parent: Forum parent
332 field_board_parent: Forum parent
333
333
334 setting_app_title: Titre de l'application
334 setting_app_title: Titre de l'application
335 setting_app_subtitle: Sous-titre de l'application
335 setting_app_subtitle: Sous-titre de l'application
336 setting_welcome_text: Texte d'accueil
336 setting_welcome_text: Texte d'accueil
337 setting_default_language: Langue par dΓ©faut
337 setting_default_language: Langue par dΓ©faut
338 setting_login_required: Authentification obligatoire
338 setting_login_required: Authentification obligatoire
339 setting_self_registration: Inscription des nouveaux utilisateurs
339 setting_self_registration: Inscription des nouveaux utilisateurs
340 setting_attachment_max_size: Taille maximale des fichiers
340 setting_attachment_max_size: Taille maximale des fichiers
341 setting_issues_export_limit: Limite d'exportation des demandes
341 setting_issues_export_limit: Limite d'exportation des demandes
342 setting_mail_from: Adresse d'Γ©mission
342 setting_mail_from: Adresse d'Γ©mission
343 setting_bcc_recipients: Destinataires en copie cachΓ©e (cci)
343 setting_bcc_recipients: Destinataires en copie cachΓ©e (cci)
344 setting_plain_text_mail: Mail en texte brut (non HTML)
344 setting_plain_text_mail: Mail en texte brut (non HTML)
345 setting_host_name: Nom d'hΓ΄te et chemin
345 setting_host_name: Nom d'hΓ΄te et chemin
346 setting_text_formatting: Formatage du texte
346 setting_text_formatting: Formatage du texte
347 setting_wiki_compression: Compression de l'historique des pages wiki
347 setting_wiki_compression: Compression de l'historique des pages wiki
348 setting_feeds_limit: Nombre maximal d'Γ©lΓ©ments dans les flux Atom
348 setting_feeds_limit: Nombre maximal d'Γ©lΓ©ments dans les flux Atom
349 setting_default_projects_public: DΓ©finir les nouveaux projets comme publics par dΓ©faut
349 setting_default_projects_public: DΓ©finir les nouveaux projets comme publics par dΓ©faut
350 setting_autofetch_changesets: RΓ©cupΓ©ration automatique des commits
350 setting_autofetch_changesets: RΓ©cupΓ©ration automatique des commits
351 setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts
351 setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts
352 setting_commit_ref_keywords: Mots-clΓ©s de rΓ©fΓ©rencement
352 setting_commit_ref_keywords: Mots-clΓ©s de rΓ©fΓ©rencement
353 setting_commit_fix_keywords: Mots-clΓ©s de rΓ©solution
353 setting_commit_fix_keywords: Mots-clΓ©s de rΓ©solution
354 setting_autologin: DurΓ©e maximale de connexion automatique
354 setting_autologin: DurΓ©e maximale de connexion automatique
355 setting_date_format: Format de date
355 setting_date_format: Format de date
356 setting_time_format: Format d'heure
356 setting_time_format: Format d'heure
357 setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets
357 setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets
358 setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes
358 setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes
359 setting_emails_footer: Pied-de-page des emails
359 setting_emails_footer: Pied-de-page des emails
360 setting_protocol: Protocole
360 setting_protocol: Protocole
361 setting_per_page_options: Options d'objets affichΓ©s par page
361 setting_per_page_options: Options d'objets affichΓ©s par page
362 setting_user_format: Format d'affichage des utilisateurs
362 setting_user_format: Format d'affichage des utilisateurs
363 setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets
363 setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets
364 setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux
364 setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux
365 setting_enabled_scm: SCM activΓ©s
365 setting_enabled_scm: SCM activΓ©s
366 setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes"
366 setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes"
367 setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails"
367 setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails"
368 setting_mail_handler_api_key: ClΓ© de protection de l'API
368 setting_mail_handler_api_key: ClΓ© de protection de l'API
369 setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels
369 setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels
370 setting_gravatar_enabled: Afficher les Gravatar des utilisateurs
370 setting_gravatar_enabled: Afficher les Gravatar des utilisateurs
371 setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es
371 setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es
372 setting_file_max_size_displayed: Taille maximum des fichiers texte affichΓ©s en ligne
372 setting_file_max_size_displayed: Taille maximum des fichiers texte affichΓ©s en ligne
373 setting_repository_log_display_limit: "Nombre maximum de rΓ©visions affichΓ©es sur l'historique d'un fichier"
373 setting_repository_log_display_limit: "Nombre maximum de rΓ©visions affichΓ©es sur l'historique d'un fichier"
374 setting_openid: "Autoriser l'authentification et l'enregistrement OpenID"
374 setting_openid: "Autoriser l'authentification et l'enregistrement OpenID"
375 setting_password_min_length: Longueur minimum des mots de passe
375 setting_password_min_length: Longueur minimum des mots de passe
376 setting_new_project_user_role_id: RΓ΄le donnΓ© Γ  un utilisateur non-administrateur qui crΓ©e un projet
376 setting_new_project_user_role_id: RΓ΄le donnΓ© Γ  un utilisateur non-administrateur qui crΓ©e un projet
377 setting_default_projects_modules: Modules activΓ©s par dΓ©faut pour les nouveaux projets
377 setting_default_projects_modules: Modules activΓ©s par dΓ©faut pour les nouveaux projets
378 setting_issue_done_ratio: Calcul de l'avancement des demandes
378 setting_issue_done_ratio: Calcul de l'avancement des demandes
379 setting_issue_done_ratio_issue_status: Utiliser le statut
379 setting_issue_done_ratio_issue_status: Utiliser le statut
380 setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectuΓ©'
380 setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectuΓ©'
381 setting_rest_api_enabled: Activer l'API REST
381 setting_rest_api_enabled: Activer l'API REST
382 setting_gravatar_default: Image Gravatar par dΓ©faut
382 setting_gravatar_default: Image Gravatar par dΓ©faut
383 setting_start_of_week: Jour de dΓ©but des calendriers
383 setting_start_of_week: Jour de dΓ©but des calendriers
384 setting_cache_formatted_text: Mettre en cache le texte formatΓ©
384 setting_cache_formatted_text: Mettre en cache le texte formatΓ©
385 setting_commit_logtime_enabled: Permettre la saisie de temps
385 setting_commit_logtime_enabled: Permettre la saisie de temps
386 setting_commit_logtime_activity_id: ActivitΓ© pour le temps saisi
386 setting_commit_logtime_activity_id: ActivitΓ© pour le temps saisi
387 setting_gantt_items_limit: Nombre maximum d'Γ©lΓ©ments affichΓ©s sur le gantt
387 setting_gantt_items_limit: Nombre maximum d'Γ©lΓ©ments affichΓ©s sur le gantt
388 setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes
388 setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes
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
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 setting_commit_cross_project_ref: Permettre le rΓ©fΓ©rencement et la rΓ©solution des demandes de tous les autres projets
390 setting_commit_cross_project_ref: Permettre le rΓ©fΓ©rencement et la rΓ©solution des demandes de tous les autres projets
391 setting_unsubscribe: Permettre aux utilisateurs de supprimer leur propre compte
391 setting_unsubscribe: Permettre aux utilisateurs de supprimer leur propre compte
392 setting_session_lifetime: DurΓ©e de vie maximale des sessions
392 setting_session_lifetime: DurΓ©e de vie maximale des sessions
393 setting_session_timeout: DurΓ©e maximale d'inactivitΓ©
393 setting_session_timeout: DurΓ©e maximale d'inactivitΓ©
394 setting_thumbnails_enabled: Afficher les vignettes des images
394 setting_thumbnails_enabled: Afficher les vignettes des images
395 setting_thumbnails_size: Taille des vignettes (en pixels)
395 setting_thumbnails_size: Taille des vignettes (en pixels)
396
396
397 permission_add_project: CrΓ©er un projet
397 permission_add_project: CrΓ©er un projet
398 permission_add_subprojects: CrΓ©er des sous-projets
398 permission_add_subprojects: CrΓ©er des sous-projets
399 permission_edit_project: Modifier le projet
399 permission_edit_project: Modifier le projet
400 permission_close_project: Fermer / rΓ©ouvrir le projet
400 permission_close_project: Fermer / rΓ©ouvrir le projet
401 permission_select_project_modules: Choisir les modules
401 permission_select_project_modules: Choisir les modules
402 permission_manage_members: GΓ©rer les membres
402 permission_manage_members: GΓ©rer les membres
403 permission_manage_versions: GΓ©rer les versions
403 permission_manage_versions: GΓ©rer les versions
404 permission_manage_categories: GΓ©rer les catΓ©gories de demandes
404 permission_manage_categories: GΓ©rer les catΓ©gories de demandes
405 permission_view_issues: Voir les demandes
405 permission_view_issues: Voir les demandes
406 permission_add_issues: CrΓ©er des demandes
406 permission_add_issues: CrΓ©er des demandes
407 permission_edit_issues: Modifier les demandes
407 permission_edit_issues: Modifier les demandes
408 permission_manage_issue_relations: GΓ©rer les relations
408 permission_manage_issue_relations: GΓ©rer les relations
409 permission_set_issues_private: Rendre les demandes publiques ou privΓ©es
409 permission_set_issues_private: Rendre les demandes publiques ou privΓ©es
410 permission_set_own_issues_private: Rendre ses propres demandes publiques ou privΓ©es
410 permission_set_own_issues_private: Rendre ses propres demandes publiques ou privΓ©es
411 permission_add_issue_notes: Ajouter des notes
411 permission_add_issue_notes: Ajouter des notes
412 permission_edit_issue_notes: Modifier les notes
412 permission_edit_issue_notes: Modifier les notes
413 permission_edit_own_issue_notes: Modifier ses propres notes
413 permission_edit_own_issue_notes: Modifier ses propres notes
414 permission_move_issues: DΓ©placer les demandes
414 permission_move_issues: DΓ©placer les demandes
415 permission_delete_issues: Supprimer les demandes
415 permission_delete_issues: Supprimer les demandes
416 permission_manage_public_queries: GΓ©rer les requΓͺtes publiques
416 permission_manage_public_queries: GΓ©rer les requΓͺtes publiques
417 permission_save_queries: Sauvegarder les requΓͺtes
417 permission_save_queries: Sauvegarder les requΓͺtes
418 permission_view_gantt: Voir le gantt
418 permission_view_gantt: Voir le gantt
419 permission_view_calendar: Voir le calendrier
419 permission_view_calendar: Voir le calendrier
420 permission_view_issue_watchers: Voir la liste des observateurs
420 permission_view_issue_watchers: Voir la liste des observateurs
421 permission_add_issue_watchers: Ajouter des observateurs
421 permission_add_issue_watchers: Ajouter des observateurs
422 permission_delete_issue_watchers: Supprimer des observateurs
422 permission_delete_issue_watchers: Supprimer des observateurs
423 permission_log_time: Saisir le temps passΓ©
423 permission_log_time: Saisir le temps passΓ©
424 permission_view_time_entries: Voir le temps passΓ©
424 permission_view_time_entries: Voir le temps passΓ©
425 permission_edit_time_entries: Modifier les temps passΓ©s
425 permission_edit_time_entries: Modifier les temps passΓ©s
426 permission_edit_own_time_entries: Modifier son propre temps passΓ©
426 permission_edit_own_time_entries: Modifier son propre temps passΓ©
427 permission_manage_news: GΓ©rer les annonces
427 permission_manage_news: GΓ©rer les annonces
428 permission_comment_news: Commenter les annonces
428 permission_comment_news: Commenter les annonces
429 permission_manage_documents: GΓ©rer les documents
429 permission_manage_documents: GΓ©rer les documents
430 permission_view_documents: Voir les documents
430 permission_view_documents: Voir les documents
431 permission_manage_files: GΓ©rer les fichiers
431 permission_manage_files: GΓ©rer les fichiers
432 permission_view_files: Voir les fichiers
432 permission_view_files: Voir les fichiers
433 permission_manage_wiki: GΓ©rer le wiki
433 permission_manage_wiki: GΓ©rer le wiki
434 permission_rename_wiki_pages: Renommer les pages
434 permission_rename_wiki_pages: Renommer les pages
435 permission_delete_wiki_pages: Supprimer les pages
435 permission_delete_wiki_pages: Supprimer les pages
436 permission_view_wiki_pages: Voir le wiki
436 permission_view_wiki_pages: Voir le wiki
437 permission_view_wiki_edits: "Voir l'historique des modifications"
437 permission_view_wiki_edits: "Voir l'historique des modifications"
438 permission_edit_wiki_pages: Modifier les pages
438 permission_edit_wiki_pages: Modifier les pages
439 permission_delete_wiki_pages_attachments: Supprimer les fichiers joints
439 permission_delete_wiki_pages_attachments: Supprimer les fichiers joints
440 permission_protect_wiki_pages: ProtΓ©ger les pages
440 permission_protect_wiki_pages: ProtΓ©ger les pages
441 permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources
441 permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources
442 permission_browse_repository: Parcourir les sources
442 permission_browse_repository: Parcourir les sources
443 permission_view_changesets: Voir les rΓ©visions
443 permission_view_changesets: Voir les rΓ©visions
444 permission_commit_access: Droit de commit
444 permission_commit_access: Droit de commit
445 permission_manage_boards: GΓ©rer les forums
445 permission_manage_boards: GΓ©rer les forums
446 permission_view_messages: Voir les messages
446 permission_view_messages: Voir les messages
447 permission_add_messages: Poster un message
447 permission_add_messages: Poster un message
448 permission_edit_messages: Modifier les messages
448 permission_edit_messages: Modifier les messages
449 permission_edit_own_messages: Modifier ses propres messages
449 permission_edit_own_messages: Modifier ses propres messages
450 permission_delete_messages: Supprimer les messages
450 permission_delete_messages: Supprimer les messages
451 permission_delete_own_messages: Supprimer ses propres messages
451 permission_delete_own_messages: Supprimer ses propres messages
452 permission_export_wiki_pages: Exporter les pages
452 permission_export_wiki_pages: Exporter les pages
453 permission_manage_project_activities: GΓ©rer les activitΓ©s
453 permission_manage_project_activities: GΓ©rer les activitΓ©s
454 permission_manage_subtasks: GΓ©rer les sous-tΓ’ches
454 permission_manage_subtasks: GΓ©rer les sous-tΓ’ches
455 permission_manage_related_issues: GΓ©rer les demandes associΓ©es
455 permission_manage_related_issues: GΓ©rer les demandes associΓ©es
456
456
457 project_module_issue_tracking: Suivi des demandes
457 project_module_issue_tracking: Suivi des demandes
458 project_module_time_tracking: Suivi du temps passΓ©
458 project_module_time_tracking: Suivi du temps passΓ©
459 project_module_news: Publication d'annonces
459 project_module_news: Publication d'annonces
460 project_module_documents: Publication de documents
460 project_module_documents: Publication de documents
461 project_module_files: Publication de fichiers
461 project_module_files: Publication de fichiers
462 project_module_wiki: Wiki
462 project_module_wiki: Wiki
463 project_module_repository: DΓ©pΓ΄t de sources
463 project_module_repository: DΓ©pΓ΄t de sources
464 project_module_boards: Forums de discussion
464 project_module_boards: Forums de discussion
465
465
466 label_user: Utilisateur
466 label_user: Utilisateur
467 label_user_plural: Utilisateurs
467 label_user_plural: Utilisateurs
468 label_user_new: Nouvel utilisateur
468 label_user_new: Nouvel utilisateur
469 label_user_anonymous: Anonyme
469 label_user_anonymous: Anonyme
470 label_project: Projet
470 label_project: Projet
471 label_project_new: Nouveau projet
471 label_project_new: Nouveau projet
472 label_project_plural: Projets
472 label_project_plural: Projets
473 label_x_projects:
473 label_x_projects:
474 zero: aucun projet
474 zero: aucun projet
475 one: un projet
475 one: un projet
476 other: "%{count} projets"
476 other: "%{count} projets"
477 label_project_all: Tous les projets
477 label_project_all: Tous les projets
478 label_project_latest: Derniers projets
478 label_project_latest: Derniers projets
479 label_issue: Demande
479 label_issue: Demande
480 label_issue_new: Nouvelle demande
480 label_issue_new: Nouvelle demande
481 label_issue_plural: Demandes
481 label_issue_plural: Demandes
482 label_issue_view_all: Voir toutes les demandes
482 label_issue_view_all: Voir toutes les demandes
483 label_issue_added: Demande ajoutΓ©e
483 label_issue_added: Demande ajoutΓ©e
484 label_issue_updated: Demande mise Γ  jour
484 label_issue_updated: Demande mise Γ  jour
485 label_issue_note_added: Note ajoutΓ©e
485 label_issue_note_added: Note ajoutΓ©e
486 label_issue_status_updated: Statut changΓ©
486 label_issue_status_updated: Statut changΓ©
487 label_issue_priority_updated: PrioritΓ© changΓ©e
487 label_issue_priority_updated: PrioritΓ© changΓ©e
488 label_issues_by: "Demandes par %{value}"
488 label_issues_by: "Demandes par %{value}"
489 label_document: Document
489 label_document: Document
490 label_document_new: Nouveau document
490 label_document_new: Nouveau document
491 label_document_plural: Documents
491 label_document_plural: Documents
492 label_document_added: Document ajoutΓ©
492 label_document_added: Document ajoutΓ©
493 label_role: RΓ΄le
493 label_role: RΓ΄le
494 label_role_plural: RΓ΄les
494 label_role_plural: RΓ΄les
495 label_role_new: Nouveau rΓ΄le
495 label_role_new: Nouveau rΓ΄le
496 label_role_and_permissions: RΓ΄les et permissions
496 label_role_and_permissions: RΓ΄les et permissions
497 label_role_anonymous: Anonyme
497 label_role_anonymous: Anonyme
498 label_role_non_member: Non membre
498 label_role_non_member: Non membre
499 label_member: Membre
499 label_member: Membre
500 label_member_new: Nouveau membre
500 label_member_new: Nouveau membre
501 label_member_plural: Membres
501 label_member_plural: Membres
502 label_tracker: Tracker
502 label_tracker: Tracker
503 label_tracker_plural: Trackers
503 label_tracker_plural: Trackers
504 label_tracker_new: Nouveau tracker
504 label_tracker_new: Nouveau tracker
505 label_workflow: Workflow
505 label_workflow: Workflow
506 label_issue_status: Statut de demandes
506 label_issue_status: Statut de demandes
507 label_issue_status_plural: Statuts de demandes
507 label_issue_status_plural: Statuts de demandes
508 label_issue_status_new: Nouveau statut
508 label_issue_status_new: Nouveau statut
509 label_issue_category: CatΓ©gorie de demandes
509 label_issue_category: CatΓ©gorie de demandes
510 label_issue_category_plural: CatΓ©gories de demandes
510 label_issue_category_plural: CatΓ©gories de demandes
511 label_issue_category_new: Nouvelle catΓ©gorie
511 label_issue_category_new: Nouvelle catΓ©gorie
512 label_custom_field: Champ personnalisΓ©
512 label_custom_field: Champ personnalisΓ©
513 label_custom_field_plural: Champs personnalisΓ©s
513 label_custom_field_plural: Champs personnalisΓ©s
514 label_custom_field_new: Nouveau champ personnalisΓ©
514 label_custom_field_new: Nouveau champ personnalisΓ©
515 label_enumerations: Listes de valeurs
515 label_enumerations: Listes de valeurs
516 label_enumeration_new: Nouvelle valeur
516 label_enumeration_new: Nouvelle valeur
517 label_information: Information
517 label_information: Information
518 label_information_plural: Informations
518 label_information_plural: Informations
519 label_please_login: Identification
519 label_please_login: Identification
520 label_register: S'enregistrer
520 label_register: S'enregistrer
521 label_login_with_open_id_option: S'authentifier avec OpenID
521 label_login_with_open_id_option: S'authentifier avec OpenID
522 label_password_lost: Mot de passe perdu
522 label_password_lost: Mot de passe perdu
523 label_home: Accueil
523 label_home: Accueil
524 label_my_page: Ma page
524 label_my_page: Ma page
525 label_my_account: Mon compte
525 label_my_account: Mon compte
526 label_my_projects: Mes projets
526 label_my_projects: Mes projets
527 label_my_page_block: Blocs disponibles
527 label_my_page_block: Blocs disponibles
528 label_administration: Administration
528 label_administration: Administration
529 label_login: Connexion
529 label_login: Connexion
530 label_logout: DΓ©connexion
530 label_logout: DΓ©connexion
531 label_help: Aide
531 label_help: Aide
532 label_reported_issues: "Demandes soumises "
532 label_reported_issues: "Demandes soumises "
533 label_assigned_to_me_issues: Demandes qui me sont assignΓ©es
533 label_assigned_to_me_issues: Demandes qui me sont assignΓ©es
534 label_last_login: "Dernière connexion "
534 label_last_login: "Dernière connexion "
535 label_registered_on: "Inscrit le "
535 label_registered_on: "Inscrit le "
536 label_activity: ActivitΓ©
536 label_activity: ActivitΓ©
537 label_overall_activity: ActivitΓ© globale
537 label_overall_activity: ActivitΓ© globale
538 label_user_activity: "ActivitΓ© de %{value}"
538 label_user_activity: "ActivitΓ© de %{value}"
539 label_new: Nouveau
539 label_new: Nouveau
540 label_logged_as: ConnectΓ© en tant que
540 label_logged_as: ConnectΓ© en tant que
541 label_environment: Environnement
541 label_environment: Environnement
542 label_authentication: Authentification
542 label_authentication: Authentification
543 label_auth_source: Mode d'authentification
543 label_auth_source: Mode d'authentification
544 label_auth_source_new: Nouveau mode d'authentification
544 label_auth_source_new: Nouveau mode d'authentification
545 label_auth_source_plural: Modes d'authentification
545 label_auth_source_plural: Modes d'authentification
546 label_subproject_plural: Sous-projets
546 label_subproject_plural: Sous-projets
547 label_subproject_new: Nouveau sous-projet
547 label_subproject_new: Nouveau sous-projet
548 label_and_its_subprojects: "%{value} et ses sous-projets"
548 label_and_its_subprojects: "%{value} et ses sous-projets"
549 label_min_max_length: Longueurs mini - maxi
549 label_min_max_length: Longueurs mini - maxi
550 label_list: Liste
550 label_list: Liste
551 label_date: Date
551 label_date: Date
552 label_integer: Entier
552 label_integer: Entier
553 label_float: Nombre dΓ©cimal
553 label_float: Nombre dΓ©cimal
554 label_boolean: BoolΓ©en
554 label_boolean: BoolΓ©en
555 label_string: Texte
555 label_string: Texte
556 label_text: Texte long
556 label_text: Texte long
557 label_attribute: Attribut
557 label_attribute: Attribut
558 label_attribute_plural: Attributs
558 label_attribute_plural: Attributs
559 label_download: "%{count} tΓ©lΓ©chargement"
559 label_download: "%{count} tΓ©lΓ©chargement"
560 label_download_plural: "%{count} tΓ©lΓ©chargements"
560 label_download_plural: "%{count} tΓ©lΓ©chargements"
561 label_no_data: Aucune donnΓ©e Γ  afficher
561 label_no_data: Aucune donnΓ©e Γ  afficher
562 label_change_status: Changer le statut
562 label_change_status: Changer le statut
563 label_history: Historique
563 label_history: Historique
564 label_attachment: Fichier
564 label_attachment: Fichier
565 label_attachment_new: Nouveau fichier
565 label_attachment_new: Nouveau fichier
566 label_attachment_delete: Supprimer le fichier
566 label_attachment_delete: Supprimer le fichier
567 label_attachment_plural: Fichiers
567 label_attachment_plural: Fichiers
568 label_file_added: Fichier ajoutΓ©
568 label_file_added: Fichier ajoutΓ©
569 label_report: Rapport
569 label_report: Rapport
570 label_report_plural: Rapports
570 label_report_plural: Rapports
571 label_news: Annonce
571 label_news: Annonce
572 label_news_new: Nouvelle annonce
572 label_news_new: Nouvelle annonce
573 label_news_plural: Annonces
573 label_news_plural: Annonces
574 label_news_latest: Dernières annonces
574 label_news_latest: Dernières annonces
575 label_news_view_all: Voir toutes les annonces
575 label_news_view_all: Voir toutes les annonces
576 label_news_added: Annonce ajoutΓ©e
576 label_news_added: Annonce ajoutΓ©e
577 label_news_comment_added: Commentaire ajoutΓ© Γ  une annonce
577 label_news_comment_added: Commentaire ajoutΓ© Γ  une annonce
578 label_settings: Configuration
578 label_settings: Configuration
579 label_overview: AperΓ§u
579 label_overview: AperΓ§u
580 label_version: Version
580 label_version: Version
581 label_version_new: Nouvelle version
581 label_version_new: Nouvelle version
582 label_version_plural: Versions
582 label_version_plural: Versions
583 label_confirmation: Confirmation
583 label_confirmation: Confirmation
584 label_export_to: 'Formats disponibles :'
584 label_export_to: 'Formats disponibles :'
585 label_read: Lire...
585 label_read: Lire...
586 label_public_projects: Projets publics
586 label_public_projects: Projets publics
587 label_open_issues: ouvert
587 label_open_issues: ouvert
588 label_open_issues_plural: ouverts
588 label_open_issues_plural: ouverts
589 label_closed_issues: fermΓ©
589 label_closed_issues: fermΓ©
590 label_closed_issues_plural: fermΓ©s
590 label_closed_issues_plural: fermΓ©s
591 label_x_open_issues_abbr_on_total:
591 label_x_open_issues_abbr_on_total:
592 zero: 0 ouverte sur %{total}
592 zero: 0 ouverte sur %{total}
593 one: 1 ouverte sur %{total}
593 one: 1 ouverte sur %{total}
594 other: "%{count} ouvertes sur %{total}"
594 other: "%{count} ouvertes sur %{total}"
595 label_x_open_issues_abbr:
595 label_x_open_issues_abbr:
596 zero: 0 ouverte
596 zero: 0 ouverte
597 one: 1 ouverte
597 one: 1 ouverte
598 other: "%{count} ouvertes"
598 other: "%{count} ouvertes"
599 label_x_closed_issues_abbr:
599 label_x_closed_issues_abbr:
600 zero: 0 fermΓ©e
600 zero: 0 fermΓ©e
601 one: 1 fermΓ©e
601 one: 1 fermΓ©e
602 other: "%{count} fermΓ©es"
602 other: "%{count} fermΓ©es"
603 label_x_issues:
603 label_x_issues:
604 zero: 0 demande
604 zero: 0 demande
605 one: 1 demande
605 one: 1 demande
606 other: "%{count} demandes"
606 other: "%{count} demandes"
607 label_total: Total
607 label_total: Total
608 label_permissions: Permissions
608 label_permissions: Permissions
609 label_current_status: Statut actuel
609 label_current_status: Statut actuel
610 label_new_statuses_allowed: Nouveaux statuts autorisΓ©s
610 label_new_statuses_allowed: Nouveaux statuts autorisΓ©s
611 label_all: tous
611 label_all: tous
612 label_none: aucun
612 label_none: aucun
613 label_nobody: personne
613 label_nobody: personne
614 label_next: Suivant
614 label_next: Suivant
615 label_previous: PrΓ©cΓ©dent
615 label_previous: PrΓ©cΓ©dent
616 label_used_by: UtilisΓ© par
616 label_used_by: UtilisΓ© par
617 label_details: DΓ©tails
617 label_details: DΓ©tails
618 label_add_note: Ajouter une note
618 label_add_note: Ajouter une note
619 label_per_page: Par page
619 label_per_page: Par page
620 label_calendar: Calendrier
620 label_calendar: Calendrier
621 label_months_from: mois depuis
621 label_months_from: mois depuis
622 label_gantt: Gantt
622 label_gantt: Gantt
623 label_internal: Interne
623 label_internal: Interne
624 label_last_changes: "%{count} derniers changements"
624 label_last_changes: "%{count} derniers changements"
625 label_change_view_all: Voir tous les changements
625 label_change_view_all: Voir tous les changements
626 label_personalize_page: Personnaliser cette page
626 label_personalize_page: Personnaliser cette page
627 label_comment: Commentaire
627 label_comment: Commentaire
628 label_comment_plural: Commentaires
628 label_comment_plural: Commentaires
629 label_x_comments:
629 label_x_comments:
630 zero: aucun commentaire
630 zero: aucun commentaire
631 one: un commentaire
631 one: un commentaire
632 other: "%{count} commentaires"
632 other: "%{count} commentaires"
633 label_comment_add: Ajouter un commentaire
633 label_comment_add: Ajouter un commentaire
634 label_comment_added: Commentaire ajoutΓ©
634 label_comment_added: Commentaire ajoutΓ©
635 label_comment_delete: Supprimer les commentaires
635 label_comment_delete: Supprimer les commentaires
636 label_query: Rapport personnalisΓ©
636 label_query: Rapport personnalisΓ©
637 label_query_plural: Rapports personnalisΓ©s
637 label_query_plural: Rapports personnalisΓ©s
638 label_query_new: Nouveau rapport
638 label_query_new: Nouveau rapport
639 label_my_queries: Mes rapports personnalisΓ©s
639 label_my_queries: Mes rapports personnalisΓ©s
640 label_filter_add: "Ajouter le filtre "
640 label_filter_add: "Ajouter le filtre "
641 label_filter_plural: Filtres
641 label_filter_plural: Filtres
642 label_equals: Γ©gal
642 label_equals: Γ©gal
643 label_not_equals: diffΓ©rent
643 label_not_equals: diffΓ©rent
644 label_in_less_than: dans moins de
644 label_in_less_than: dans moins de
645 label_in_more_than: dans plus de
645 label_in_more_than: dans plus de
646 label_in: dans
646 label_in: dans
647 label_today: aujourd'hui
647 label_today: aujourd'hui
648 label_all_time: toute la pΓ©riode
648 label_all_time: toute la pΓ©riode
649 label_yesterday: hier
649 label_yesterday: hier
650 label_this_week: cette semaine
650 label_this_week: cette semaine
651 label_last_week: la semaine dernière
651 label_last_week: la semaine dernière
652 label_last_n_days: "les %{count} derniers jours"
652 label_last_n_days: "les %{count} derniers jours"
653 label_this_month: ce mois-ci
653 label_this_month: ce mois-ci
654 label_last_month: le mois dernier
654 label_last_month: le mois dernier
655 label_this_year: cette annΓ©e
655 label_this_year: cette annΓ©e
656 label_date_range: PΓ©riode
656 label_date_range: PΓ©riode
657 label_less_than_ago: il y a moins de
657 label_less_than_ago: il y a moins de
658 label_more_than_ago: il y a plus de
658 label_more_than_ago: il y a plus de
659 label_ago: il y a
659 label_ago: il y a
660 label_contains: contient
660 label_contains: contient
661 label_not_contains: ne contient pas
661 label_not_contains: ne contient pas
662 label_day_plural: jours
662 label_day_plural: jours
663 label_repository: DΓ©pΓ΄t
663 label_repository: DΓ©pΓ΄t
664 label_repository_new: Nouveau dΓ©pΓ΄t
664 label_repository_new: Nouveau dΓ©pΓ΄t
665 label_repository_plural: DΓ©pΓ΄ts
665 label_repository_plural: DΓ©pΓ΄ts
666 label_browse: Parcourir
666 label_browse: Parcourir
667 label_modification: "%{count} modification"
667 label_modification: "%{count} modification"
668 label_modification_plural: "%{count} modifications"
668 label_modification_plural: "%{count} modifications"
669 label_revision: "RΓ©vision "
669 label_revision: "RΓ©vision "
670 label_revision_plural: RΓ©visions
670 label_revision_plural: RΓ©visions
671 label_associated_revisions: RΓ©visions associΓ©es
671 label_associated_revisions: RΓ©visions associΓ©es
672 label_added: ajoutΓ©
672 label_added: ajoutΓ©
673 label_modified: modifiΓ©
673 label_modified: modifiΓ©
674 label_copied: copiΓ©
674 label_copied: copiΓ©
675 label_renamed: renommΓ©
675 label_renamed: renommΓ©
676 label_deleted: supprimΓ©
676 label_deleted: supprimΓ©
677 label_latest_revision: Dernière révision
677 label_latest_revision: Dernière révision
678 label_latest_revision_plural: Dernières révisions
678 label_latest_revision_plural: Dernières révisions
679 label_view_revisions: Voir les rΓ©visions
679 label_view_revisions: Voir les rΓ©visions
680 label_max_size: Taille maximale
680 label_max_size: Taille maximale
681 label_sort_highest: Remonter en premier
681 label_sort_highest: Remonter en premier
682 label_sort_higher: Remonter
682 label_sort_higher: Remonter
683 label_sort_lower: Descendre
683 label_sort_lower: Descendre
684 label_sort_lowest: Descendre en dernier
684 label_sort_lowest: Descendre en dernier
685 label_roadmap: Roadmap
685 label_roadmap: Roadmap
686 label_roadmap_due_in: "Γ‰chΓ©ance dans %{value}"
686 label_roadmap_due_in: "Γ‰chΓ©ance dans %{value}"
687 label_roadmap_overdue: "En retard de %{value}"
687 label_roadmap_overdue: "En retard de %{value}"
688 label_roadmap_no_issues: Aucune demande pour cette version
688 label_roadmap_no_issues: Aucune demande pour cette version
689 label_search: "Recherche "
689 label_search: "Recherche "
690 label_result_plural: RΓ©sultats
690 label_result_plural: RΓ©sultats
691 label_all_words: Tous les mots
691 label_all_words: Tous les mots
692 label_wiki: Wiki
692 label_wiki: Wiki
693 label_wiki_edit: RΓ©vision wiki
693 label_wiki_edit: RΓ©vision wiki
694 label_wiki_edit_plural: RΓ©visions wiki
694 label_wiki_edit_plural: RΓ©visions wiki
695 label_wiki_page: Page wiki
695 label_wiki_page: Page wiki
696 label_wiki_page_plural: Pages wiki
696 label_wiki_page_plural: Pages wiki
697 label_index_by_title: Index par titre
697 label_index_by_title: Index par titre
698 label_index_by_date: Index par date
698 label_index_by_date: Index par date
699 label_current_version: Version actuelle
699 label_current_version: Version actuelle
700 label_preview: PrΓ©visualisation
700 label_preview: PrΓ©visualisation
701 label_feed_plural: Flux RSS
701 label_feed_plural: Flux RSS
702 label_changes_details: DΓ©tails de tous les changements
702 label_changes_details: DΓ©tails de tous les changements
703 label_issue_tracking: Suivi des demandes
703 label_issue_tracking: Suivi des demandes
704 label_spent_time: Temps passΓ©
704 label_spent_time: Temps passΓ©
705 label_f_hour: "%{value} heure"
705 label_f_hour: "%{value} heure"
706 label_f_hour_plural: "%{value} heures"
706 label_f_hour_plural: "%{value} heures"
707 label_time_tracking: Suivi du temps
707 label_time_tracking: Suivi du temps
708 label_change_plural: Changements
708 label_change_plural: Changements
709 label_statistics: Statistiques
709 label_statistics: Statistiques
710 label_commits_per_month: Commits par mois
710 label_commits_per_month: Commits par mois
711 label_commits_per_author: Commits par auteur
711 label_commits_per_author: Commits par auteur
712 label_view_diff: Voir les diffΓ©rences
712 label_view_diff: Voir les diffΓ©rences
713 label_diff_inline: en ligne
713 label_diff_inline: en ligne
714 label_diff_side_by_side: cΓ΄te Γ  cΓ΄te
714 label_diff_side_by_side: cΓ΄te Γ  cΓ΄te
715 label_options: Options
715 label_options: Options
716 label_copy_workflow_from: Copier le workflow de
716 label_copy_workflow_from: Copier le workflow de
717 label_permissions_report: Synthèse des permissions
717 label_permissions_report: Synthèse des permissions
718 label_watched_issues: Demandes surveillΓ©es
718 label_watched_issues: Demandes surveillΓ©es
719 label_related_issues: Demandes liΓ©es
719 label_related_issues: Demandes liΓ©es
720 label_applied_status: Statut appliquΓ©
720 label_applied_status: Statut appliquΓ©
721 label_loading: Chargement...
721 label_loading: Chargement...
722 label_relation_new: Nouvelle relation
722 label_relation_new: Nouvelle relation
723 label_relation_delete: Supprimer la relation
723 label_relation_delete: Supprimer la relation
724 label_relates_to: liΓ© Γ 
724 label_relates_to: liΓ© Γ 
725 label_duplicates: duplique
725 label_duplicates: duplique
726 label_duplicated_by: dupliquΓ© par
726 label_duplicated_by: dupliquΓ© par
727 label_blocks: bloque
727 label_blocks: bloque
728 label_blocked_by: bloquΓ© par
728 label_blocked_by: bloquΓ© par
729 label_precedes: précède
729 label_precedes: précède
730 label_follows: suit
730 label_follows: suit
731 label_end_to_start: fin Γ  dΓ©but
731 label_end_to_start: fin Γ  dΓ©but
732 label_end_to_end: fin Γ  fin
732 label_end_to_end: fin Γ  fin
733 label_start_to_start: dΓ©but Γ  dΓ©but
733 label_start_to_start: dΓ©but Γ  dΓ©but
734 label_start_to_end: dΓ©but Γ  fin
734 label_start_to_end: dΓ©but Γ  fin
735 label_stay_logged_in: Rester connectΓ©
735 label_stay_logged_in: Rester connectΓ©
736 label_disabled: dΓ©sactivΓ©
736 label_disabled: dΓ©sactivΓ©
737 label_show_completed_versions: Voir les versions passΓ©es
737 label_show_completed_versions: Voir les versions passΓ©es
738 label_me: moi
738 label_me: moi
739 label_board: Forum
739 label_board: Forum
740 label_board_new: Nouveau forum
740 label_board_new: Nouveau forum
741 label_board_plural: Forums
741 label_board_plural: Forums
742 label_topic_plural: Discussions
742 label_topic_plural: Discussions
743 label_message_plural: Messages
743 label_message_plural: Messages
744 label_message_last: Dernier message
744 label_message_last: Dernier message
745 label_message_new: Nouveau message
745 label_message_new: Nouveau message
746 label_message_posted: Message ajoutΓ©
746 label_message_posted: Message ajoutΓ©
747 label_reply_plural: RΓ©ponses
747 label_reply_plural: RΓ©ponses
748 label_send_information: Envoyer les informations Γ  l'utilisateur
748 label_send_information: Envoyer les informations Γ  l'utilisateur
749 label_year: AnnΓ©e
749 label_year: AnnΓ©e
750 label_month: Mois
750 label_month: Mois
751 label_week: Semaine
751 label_week: Semaine
752 label_date_from: Du
752 label_date_from: Du
753 label_date_to: Au
753 label_date_to: Au
754 label_language_based: BasΓ© sur la langue de l'utilisateur
754 label_language_based: BasΓ© sur la langue de l'utilisateur
755 label_sort_by: "Trier par %{value}"
755 label_sort_by: "Trier par %{value}"
756 label_send_test_email: Envoyer un email de test
756 label_send_test_email: Envoyer un email de test
757 label_feeds_access_key_created_on: "Clé d'accès RSS créée il y a %{value}"
757 label_feeds_access_key_created_on: "Clé d'accès RSS créée il y a %{value}"
758 label_module_plural: Modules
758 label_module_plural: Modules
759 label_added_time_by: "AjoutΓ© par %{author} il y a %{age}"
759 label_added_time_by: "AjoutΓ© par %{author} il y a %{age}"
760 label_updated_time_by: "Mis Γ  jour par %{author} il y a %{age}"
760 label_updated_time_by: "Mis Γ  jour par %{author} il y a %{age}"
761 label_updated_time: "Mis Γ  jour il y a %{value}"
761 label_updated_time: "Mis Γ  jour il y a %{value}"
762 label_jump_to_a_project: Aller Γ  un projet...
762 label_jump_to_a_project: Aller Γ  un projet...
763 label_file_plural: Fichiers
763 label_file_plural: Fichiers
764 label_changeset_plural: RΓ©visions
764 label_changeset_plural: RΓ©visions
765 label_default_columns: Colonnes par dΓ©faut
765 label_default_columns: Colonnes par dΓ©faut
766 label_no_change_option: (Pas de changement)
766 label_no_change_option: (Pas de changement)
767 label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es
767 label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es
768 label_theme: Thème
768 label_theme: Thème
769 label_default: DΓ©faut
769 label_default: DΓ©faut
770 label_search_titles_only: Uniquement dans les titres
770 label_search_titles_only: Uniquement dans les titres
771 label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets"
771 label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets"
772 label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..."
772 label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..."
773 label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue"
773 label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue"
774 label_registration_activation_by_email: activation du compte par email
774 label_registration_activation_by_email: activation du compte par email
775 label_registration_manual_activation: activation manuelle du compte
775 label_registration_manual_activation: activation manuelle du compte
776 label_registration_automatic_activation: activation automatique du compte
776 label_registration_automatic_activation: activation automatique du compte
777 label_display_per_page: "Par page : %{value}"
777 label_display_per_page: "Par page : %{value}"
778 label_age: Γ‚ge
778 label_age: Γ‚ge
779 label_change_properties: Changer les propriΓ©tΓ©s
779 label_change_properties: Changer les propriΓ©tΓ©s
780 label_general: GΓ©nΓ©ral
780 label_general: GΓ©nΓ©ral
781 label_more: Plus
781 label_more: Plus
782 label_scm: SCM
782 label_scm: SCM
783 label_plugins: Plugins
783 label_plugins: Plugins
784 label_ldap_authentication: Authentification LDAP
784 label_ldap_authentication: Authentification LDAP
785 label_downloads_abbr: D/L
785 label_downloads_abbr: D/L
786 label_optional_description: Description facultative
786 label_optional_description: Description facultative
787 label_add_another_file: Ajouter un autre fichier
787 label_add_another_file: Ajouter un autre fichier
788 label_preferences: PrΓ©fΓ©rences
788 label_preferences: PrΓ©fΓ©rences
789 label_chronological_order: Dans l'ordre chronologique
789 label_chronological_order: Dans l'ordre chronologique
790 label_reverse_chronological_order: Dans l'ordre chronologique inverse
790 label_reverse_chronological_order: Dans l'ordre chronologique inverse
791 label_planning: Planning
791 label_planning: Planning
792 label_incoming_emails: Emails entrants
792 label_incoming_emails: Emails entrants
793 label_generate_key: GΓ©nΓ©rer une clΓ©
793 label_generate_key: GΓ©nΓ©rer une clΓ©
794 label_issue_watchers: Observateurs
794 label_issue_watchers: Observateurs
795 label_example: Exemple
795 label_example: Exemple
796 label_display: Affichage
796 label_display: Affichage
797 label_sort: Tri
797 label_sort: Tri
798 label_ascending: Croissant
798 label_ascending: Croissant
799 label_descending: DΓ©croissant
799 label_descending: DΓ©croissant
800 label_date_from_to: Du %{start} au %{end}
800 label_date_from_to: Du %{start} au %{end}
801 label_wiki_content_added: Page wiki ajoutΓ©e
801 label_wiki_content_added: Page wiki ajoutΓ©e
802 label_wiki_content_updated: Page wiki mise Γ  jour
802 label_wiki_content_updated: Page wiki mise Γ  jour
803 label_group_plural: Groupes
803 label_group_plural: Groupes
804 label_group: Groupe
804 label_group: Groupe
805 label_group_new: Nouveau groupe
805 label_group_new: Nouveau groupe
806 label_time_entry_plural: Temps passΓ©
806 label_time_entry_plural: Temps passΓ©
807 label_version_sharing_none: Non partagΓ©
807 label_version_sharing_none: Non partagΓ©
808 label_version_sharing_descendants: Avec les sous-projets
808 label_version_sharing_descendants: Avec les sous-projets
809 label_version_sharing_hierarchy: Avec toute la hiΓ©rarchie
809 label_version_sharing_hierarchy: Avec toute la hiΓ©rarchie
810 label_version_sharing_tree: Avec tout l'arbre
810 label_version_sharing_tree: Avec tout l'arbre
811 label_version_sharing_system: Avec tous les projets
811 label_version_sharing_system: Avec tous les projets
812 label_copy_source: Source
812 label_copy_source: Source
813 label_copy_target: Cible
813 label_copy_target: Cible
814 label_copy_same_as_target: Comme la cible
814 label_copy_same_as_target: Comme la cible
815 label_update_issue_done_ratios: Mettre Γ  jour l'avancement des demandes
815 label_update_issue_done_ratios: Mettre Γ  jour l'avancement des demandes
816 label_display_used_statuses_only: N'afficher que les statuts utilisΓ©s dans ce tracker
816 label_display_used_statuses_only: N'afficher que les statuts utilisΓ©s dans ce tracker
817 label_api_access_key: Clé d'accès API
817 label_api_access_key: Clé d'accès API
818 label_api_access_key_created_on: Clé d'accès API créée il y a %{value}
818 label_api_access_key_created_on: Clé d'accès API créée il y a %{value}
819 label_feeds_access_key: Clé d'accès RSS
819 label_feeds_access_key: Clé d'accès RSS
820 label_missing_api_access_key: Clé d'accès API manquante
820 label_missing_api_access_key: Clé d'accès API manquante
821 label_missing_feeds_access_key: Clé d'accès RSS manquante
821 label_missing_feeds_access_key: Clé d'accès RSS manquante
822 label_close_versions: Fermer les versions terminΓ©es
822 label_close_versions: Fermer les versions terminΓ©es
823 label_revision_id: RΓ©vision %{value}
823 label_revision_id: RΓ©vision %{value}
824 label_profile: Profil
824 label_profile: Profil
825 label_subtask_plural: Sous-tΓ’ches
825 label_subtask_plural: Sous-tΓ’ches
826 label_project_copy_notifications: Envoyer les notifications durant la copie du projet
826 label_project_copy_notifications: Envoyer les notifications durant la copie du projet
827 label_principal_search: "Rechercher un utilisateur ou un groupe :"
827 label_principal_search: "Rechercher un utilisateur ou un groupe :"
828 label_user_search: "Rechercher un utilisateur :"
828 label_user_search: "Rechercher un utilisateur :"
829 label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande
829 label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande
830 label_additional_workflow_transitions_for_assignee: Autorisations supplΓ©mentaires lorsque la demande est assignΓ©e Γ  l'utilisateur
830 label_additional_workflow_transitions_for_assignee: Autorisations supplΓ©mentaires lorsque la demande est assignΓ©e Γ  l'utilisateur
831 label_issues_visibility_all: Toutes les demandes
831 label_issues_visibility_all: Toutes les demandes
832 label_issues_visibility_public: Toutes les demandes non privΓ©es
832 label_issues_visibility_public: Toutes les demandes non privΓ©es
833 label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur
833 label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur
834 label_export_options: Options d'exportation %{export_format}
834 label_export_options: Options d'exportation %{export_format}
835 label_copy_attachments: Copier les fichiers
835 label_copy_attachments: Copier les fichiers
836 label_item_position: "%{position} sur %{count}"
836 label_item_position: "%{position} sur %{count}"
837 label_completed_versions: Versions passΓ©es
837 label_completed_versions: Versions passΓ©es
838 label_session_expiration: Expiration des sessions
838 label_session_expiration: Expiration des sessions
839 label_show_closed_projects: Voir les projets fermΓ©s
839 label_show_closed_projects: Voir les projets fermΓ©s
840 label_status_transitions: Changements de statut
840 label_status_transitions: Changements de statut
841 label_fields_permissions: Permissions sur les champs
841 label_fields_permissions: Permissions sur les champs
842 label_readonly: Lecture
842 label_readonly: Lecture
843 label_required: Obligatoire
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 button_login: Connexion
849 button_login: Connexion
846 button_submit: Soumettre
850 button_submit: Soumettre
847 button_save: Sauvegarder
851 button_save: Sauvegarder
848 button_check_all: Tout cocher
852 button_check_all: Tout cocher
849 button_uncheck_all: Tout dΓ©cocher
853 button_uncheck_all: Tout dΓ©cocher
850 button_collapse_all: Plier tout
854 button_collapse_all: Plier tout
851 button_expand_all: DΓ©plier tout
855 button_expand_all: DΓ©plier tout
852 button_delete: Supprimer
856 button_delete: Supprimer
853 button_create: CrΓ©er
857 button_create: CrΓ©er
854 button_create_and_continue: CrΓ©er et continuer
858 button_create_and_continue: CrΓ©er et continuer
855 button_test: Tester
859 button_test: Tester
856 button_edit: Modifier
860 button_edit: Modifier
857 button_add: Ajouter
861 button_add: Ajouter
858 button_change: Changer
862 button_change: Changer
859 button_apply: Appliquer
863 button_apply: Appliquer
860 button_clear: Effacer
864 button_clear: Effacer
861 button_lock: Verrouiller
865 button_lock: Verrouiller
862 button_unlock: DΓ©verrouiller
866 button_unlock: DΓ©verrouiller
863 button_download: TΓ©lΓ©charger
867 button_download: TΓ©lΓ©charger
864 button_list: Lister
868 button_list: Lister
865 button_view: Voir
869 button_view: Voir
866 button_move: DΓ©placer
870 button_move: DΓ©placer
867 button_move_and_follow: DΓ©placer et suivre
871 button_move_and_follow: DΓ©placer et suivre
868 button_back: Retour
872 button_back: Retour
869 button_cancel: Annuler
873 button_cancel: Annuler
870 button_activate: Activer
874 button_activate: Activer
871 button_sort: Trier
875 button_sort: Trier
872 button_log_time: Saisir temps
876 button_log_time: Saisir temps
873 button_rollback: Revenir Γ  cette version
877 button_rollback: Revenir Γ  cette version
874 button_watch: Surveiller
878 button_watch: Surveiller
875 button_unwatch: Ne plus surveiller
879 button_unwatch: Ne plus surveiller
876 button_reply: RΓ©pondre
880 button_reply: RΓ©pondre
877 button_archive: Archiver
881 button_archive: Archiver
878 button_unarchive: DΓ©sarchiver
882 button_unarchive: DΓ©sarchiver
879 button_reset: RΓ©initialiser
883 button_reset: RΓ©initialiser
880 button_rename: Renommer
884 button_rename: Renommer
881 button_change_password: Changer de mot de passe
885 button_change_password: Changer de mot de passe
882 button_copy: Copier
886 button_copy: Copier
883 button_copy_and_follow: Copier et suivre
887 button_copy_and_follow: Copier et suivre
884 button_annotate: Annoter
888 button_annotate: Annoter
885 button_update: Mettre Γ  jour
889 button_update: Mettre Γ  jour
886 button_configure: Configurer
890 button_configure: Configurer
887 button_quote: Citer
891 button_quote: Citer
888 button_duplicate: Dupliquer
892 button_duplicate: Dupliquer
889 button_show: Afficher
893 button_show: Afficher
890 button_edit_section: Modifier cette section
894 button_edit_section: Modifier cette section
891 button_export: Exporter
895 button_export: Exporter
892 button_delete_my_account: Supprimer mon compte
896 button_delete_my_account: Supprimer mon compte
893 button_close: Fermer
897 button_close: Fermer
894 button_reopen: RΓ©ouvrir
898 button_reopen: RΓ©ouvrir
895
899
896 status_active: actif
900 status_active: actif
897 status_registered: enregistrΓ©
901 status_registered: enregistrΓ©
898 status_locked: verrouillΓ©
902 status_locked: verrouillΓ©
899
903
900 project_status_active: actif
904 project_status_active: actif
901 project_status_closed: fermΓ©
905 project_status_closed: fermΓ©
902 project_status_archived: archivΓ©
906 project_status_archived: archivΓ©
903
907
904 version_status_open: ouvert
908 version_status_open: ouvert
905 version_status_locked: verrouillΓ©
909 version_status_locked: verrouillΓ©
906 version_status_closed: fermΓ©
910 version_status_closed: fermΓ©
907
911
908 text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e
912 text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e
909 text_regexp_info: ex. ^[A-Z0-9]+$
913 text_regexp_info: ex. ^[A-Z0-9]+$
910 text_min_max_length_info: 0 pour aucune restriction
914 text_min_max_length_info: 0 pour aucune restriction
911 text_project_destroy_confirmation: Êtes-vous sûr de vouloir supprimer ce projet et toutes ses données ?
915 text_project_destroy_confirmation: Êtes-vous sûr de vouloir supprimer ce projet et toutes ses données ?
912 text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront Γ©galement supprimΓ©s."
916 text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront Γ©galement supprimΓ©s."
913 text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow
917 text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow
914 text_are_you_sure: Êtes-vous sûr ?
918 text_are_you_sure: Êtes-vous sûr ?
915 text_tip_issue_begin_day: tΓ’che commenΓ§ant ce jour
919 text_tip_issue_begin_day: tΓ’che commenΓ§ant ce jour
916 text_tip_issue_end_day: tΓ’che finissant ce jour
920 text_tip_issue_end_day: tΓ’che finissant ce jour
917 text_tip_issue_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour
921 text_tip_issue_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour
918 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Γ©.'
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 text_caracters_maximum: "%{count} caractères maximum."
923 text_caracters_maximum: "%{count} caractères maximum."
920 text_caracters_minimum: "%{count} caractères minimum."
924 text_caracters_minimum: "%{count} caractères minimum."
921 text_length_between: "Longueur comprise entre %{min} et %{max} caractères."
925 text_length_between: "Longueur comprise entre %{min} et %{max} caractères."
922 text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker
926 text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker
923 text_unallowed_characters: Caractères non autorisés
927 text_unallowed_characters: Caractères non autorisés
924 text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules).
928 text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules).
925 text_line_separated: Plusieurs valeurs possibles (une valeur par ligne).
929 text_line_separated: Plusieurs valeurs possibles (une valeur par ligne).
926 text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits
930 text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits
927 text_issue_added: "La demande %{id} a Γ©tΓ© soumise par %{author}."
931 text_issue_added: "La demande %{id} a Γ©tΓ© soumise par %{author}."
928 text_issue_updated: "La demande %{id} a Γ©tΓ© mise Γ  jour par %{author}."
932 text_issue_updated: "La demande %{id} a Γ©tΓ© mise Γ  jour par %{author}."
929 text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ?
933 text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ?
930 text_issue_category_destroy_question: "%{count} demandes sont affectΓ©es Γ  cette catΓ©gorie. Que voulez-vous faire ?"
934 text_issue_category_destroy_question: "%{count} demandes sont affectΓ©es Γ  cette catΓ©gorie. Que voulez-vous faire ?"
931 text_issue_category_destroy_assignments: N'affecter les demandes Γ  aucune autre catΓ©gorie
935 text_issue_category_destroy_assignments: N'affecter les demandes Γ  aucune autre catΓ©gorie
932 text_issue_category_reassign_to: RΓ©affecter les demandes Γ  cette catΓ©gorie
936 text_issue_category_reassign_to: RΓ©affecter les demandes Γ  cette catΓ©gorie
933 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)."
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 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Γ©."
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 text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut
939 text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut
936 text_status_changed_by_changeset: "AppliquΓ© par commit %{value}."
940 text_status_changed_by_changeset: "AppliquΓ© par commit %{value}."
937 text_time_logged_by_changeset: "AppliquΓ© par commit %{value}"
941 text_time_logged_by_changeset: "AppliquΓ© par commit %{value}"
938 text_issues_destroy_confirmation: 'Êtes-vous sûr de vouloir supprimer la ou les demandes(s) selectionnée(s) ?'
942 text_issues_destroy_confirmation: 'Êtes-vous sûr de vouloir supprimer la ou les demandes(s) selectionnée(s) ?'
939 text_issues_destroy_descendants_confirmation: "Cela entrainera Γ©galement la suppression de %{count} sous-tΓ’che(s)."
943 text_issues_destroy_descendants_confirmation: "Cela entrainera Γ©galement la suppression de %{count} sous-tΓ’che(s)."
940 text_select_project_modules: 'SΓ©lectionner les modules Γ  activer pour ce projet :'
944 text_select_project_modules: 'SΓ©lectionner les modules Γ  activer pour ce projet :'
941 text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ©
945 text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ©
942 text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture
946 text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture
943 text_plugin_assets_writable: RΓ©pertoire public des plugins accessible en Γ©criture
947 text_plugin_assets_writable: RΓ©pertoire public des plugins accessible en Γ©criture
944 text_rmagick_available: Bibliothèque RMagick présente (optionnelle)
948 text_rmagick_available: Bibliothèque RMagick présente (optionnelle)
945 text_destroy_time_entries_question: "%{hours} heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ  supprimer. Que voulez-vous faire ?"
949 text_destroy_time_entries_question: "%{hours} heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ  supprimer. Que voulez-vous faire ?"
946 text_destroy_time_entries: Supprimer les heures
950 text_destroy_time_entries: Supprimer les heures
947 text_assign_time_entries_to_project: Reporter les heures sur le projet
951 text_assign_time_entries_to_project: Reporter les heures sur le projet
948 text_reassign_time_entries: 'Reporter les heures sur cette demande:'
952 text_reassign_time_entries: 'Reporter les heures sur cette demande:'
949 text_user_wrote: "%{value} a Γ©crit :"
953 text_user_wrote: "%{value} a Γ©crit :"
950 text_enumeration_destroy_question: "Cette valeur est affectΓ©e Γ  %{count} objets."
954 text_enumeration_destroy_question: "Cette valeur est affectΓ©e Γ  %{count} objets."
951 text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ  cette valeur:'
955 text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ  cette valeur:'
952 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."
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 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."
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 text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.'
958 text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.'
955 text_custom_field_possible_values_info: 'Une ligne par valeur'
959 text_custom_field_possible_values_info: 'Une ligne par valeur'
956 text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?"
960 text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?"
957 text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines"
961 text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines"
958 text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes"
962 text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes"
959 text_wiki_page_reassign_children: "RΓ©affecter les sous-pages Γ  cette page"
963 text_wiki_page_reassign_children: "RΓ©affecter les sous-pages Γ  cette page"
960 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 ?"
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 text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardΓ© qui sera perdu si vous quittez la page."
965 text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardΓ© qui sera perdu si vous quittez la page."
962 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)"
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 text_issue_conflict_resolution_add_notes: "Ajouter mes notes et ignorer mes autres changements"
967 text_issue_conflict_resolution_add_notes: "Ajouter mes notes et ignorer mes autres changements"
964 text_issue_conflict_resolution_cancel: "Annuler ma mise Γ  jour et rΓ©afficher %{link}"
968 text_issue_conflict_resolution_cancel: "Annuler ma mise Γ  jour et rΓ©afficher %{link}"
965 text_account_destroy_confirmation: "Êtes-vous sûr de vouloir continuer ?\nVotre compte sera définitivement supprimé, sans aucune possibilité de le réactiver."
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 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."
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 text_project_closed: Ce projet est fermΓ© et accessible en lecture seule.
971 text_project_closed: Ce projet est fermΓ© et accessible en lecture seule.
968
972
969 default_role_manager: "Manager "
973 default_role_manager: "Manager "
970 default_role_developer: "DΓ©veloppeur "
974 default_role_developer: "DΓ©veloppeur "
971 default_role_reporter: "Rapporteur "
975 default_role_reporter: "Rapporteur "
972 default_tracker_bug: Anomalie
976 default_tracker_bug: Anomalie
973 default_tracker_feature: Evolution
977 default_tracker_feature: Evolution
974 default_tracker_support: Assistance
978 default_tracker_support: Assistance
975 default_issue_status_new: Nouveau
979 default_issue_status_new: Nouveau
976 default_issue_status_in_progress: En cours
980 default_issue_status_in_progress: En cours
977 default_issue_status_resolved: RΓ©solu
981 default_issue_status_resolved: RΓ©solu
978 default_issue_status_feedback: Commentaire
982 default_issue_status_feedback: Commentaire
979 default_issue_status_closed: FermΓ©
983 default_issue_status_closed: FermΓ©
980 default_issue_status_rejected: RejetΓ©
984 default_issue_status_rejected: RejetΓ©
981 default_doc_category_user: Documentation utilisateur
985 default_doc_category_user: Documentation utilisateur
982 default_doc_category_tech: Documentation technique
986 default_doc_category_tech: Documentation technique
983 default_priority_low: Bas
987 default_priority_low: Bas
984 default_priority_normal: Normal
988 default_priority_normal: Normal
985 default_priority_high: Haut
989 default_priority_high: Haut
986 default_priority_urgent: Urgent
990 default_priority_urgent: Urgent
987 default_priority_immediate: ImmΓ©diat
991 default_priority_immediate: ImmΓ©diat
988 default_activity_design: Conception
992 default_activity_design: Conception
989 default_activity_development: DΓ©veloppement
993 default_activity_development: DΓ©veloppement
990
994
991 enumeration_issue_priorities: PrioritΓ©s des demandes
995 enumeration_issue_priorities: PrioritΓ©s des demandes
992 enumeration_doc_categories: CatΓ©gories des documents
996 enumeration_doc_categories: CatΓ©gories des documents
993 enumeration_activities: ActivitΓ©s (suivi du temps)
997 enumeration_activities: ActivitΓ©s (suivi du temps)
994 label_greater_or_equal: ">="
998 label_greater_or_equal: ">="
995 label_less_or_equal: "<="
999 label_less_or_equal: "<="
996 label_between: entre
1000 label_between: entre
997 label_view_all_revisions: Voir toutes les rΓ©visions
1001 label_view_all_revisions: Voir toutes les rΓ©visions
998 label_tag: Tag
1002 label_tag: Tag
999 label_branch: Branche
1003 label_branch: Branche
1000 error_no_tracker_in_project: "Aucun tracker n'est associΓ© Γ  ce projet. VΓ©rifier la configuration du projet."
1004 error_no_tracker_in_project: "Aucun tracker n'est associΓ© Γ  ce projet. VΓ©rifier la configuration du projet."
1001 error_no_default_issue_status: "Aucun statut de demande n'est dΓ©fini par dΓ©faut. VΓ©rifier votre configuration (Administration -> Statuts de demandes)."
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 text_journal_changed: "%{label} changΓ© de %{old} Γ  %{new}"
1006 text_journal_changed: "%{label} changΓ© de %{old} Γ  %{new}"
1003 text_journal_changed_no_detail: "%{label} mis Γ  jour"
1007 text_journal_changed_no_detail: "%{label} mis Γ  jour"
1004 text_journal_set_to: "%{label} mis Γ  %{value}"
1008 text_journal_set_to: "%{label} mis Γ  %{value}"
1005 text_journal_deleted: "%{label} %{old} supprimΓ©"
1009 text_journal_deleted: "%{label} %{old} supprimΓ©"
1006 text_journal_added: "%{label} %{value} ajoutΓ©"
1010 text_journal_added: "%{label} %{value} ajoutΓ©"
1007 enumeration_system_activity: Activité système
1011 enumeration_system_activity: Activité système
1008 label_board_sticky: Sticky
1012 label_board_sticky: Sticky
1009 label_board_locked: VerrouillΓ©
1013 label_board_locked: VerrouillΓ©
1010 error_unable_delete_issue_status: Impossible de supprimer le statut de demande
1014 error_unable_delete_issue_status: Impossible de supprimer le statut de demande
1011 error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisΓ©
1015 error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisΓ©
1012 error_unable_to_connect: Connexion impossible (%{value})
1016 error_unable_to_connect: Connexion impossible (%{value})
1013 error_can_not_remove_role: Ce rΓ΄le est utilisΓ© et ne peut pas Γͺtre supprimΓ©.
1017 error_can_not_remove_role: Ce rΓ΄le est utilisΓ© et ne peut pas Γͺtre supprimΓ©.
1014 error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas Γͺtre supprimΓ©.
1018 error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas Γͺtre supprimΓ©.
1015 field_principal: Principal
1019 field_principal: Principal
1016 notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}."
1020 notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}."
1017 text_zoom_out: Zoom arrière
1021 text_zoom_out: Zoom arrière
1018 text_zoom_in: Zoom avant
1022 text_zoom_in: Zoom avant
1019 notice_unable_delete_time_entry: Impossible de supprimer le temps passΓ©.
1023 notice_unable_delete_time_entry: Impossible de supprimer le temps passΓ©.
1020 label_overall_spent_time: Temps passΓ© global
1024 label_overall_spent_time: Temps passΓ© global
1021 field_time_entries: Temps passΓ©
1025 field_time_entries: Temps passΓ©
1022 project_module_gantt: Gantt
1026 project_module_gantt: Gantt
1023 project_module_calendar: Calendrier
1027 project_module_calendar: Calendrier
1024 button_edit_associated_wikipage: "Modifier la page wiki associΓ©e: %{page_title}"
1028 button_edit_associated_wikipage: "Modifier la page wiki associΓ©e: %{page_title}"
1025 text_are_you_sure_with_children: Supprimer la demande et toutes ses sous-demandes ?
1029 text_are_you_sure_with_children: Supprimer la demande et toutes ses sous-demandes ?
1026 field_text: Champ texte
1030 field_text: Champ texte
1027 label_user_mail_option_only_owner: Seulement pour ce que j'ai créé
1031 label_user_mail_option_only_owner: Seulement pour ce que j'ai créé
1028 setting_default_notification_option: Option de notification par dΓ©faut
1032 setting_default_notification_option: Option de notification par dΓ©faut
1029 label_user_mail_option_only_my_events: Seulement pour ce que je surveille
1033 label_user_mail_option_only_my_events: Seulement pour ce que je surveille
1030 label_user_mail_option_only_assigned: Seulement pour ce qui m'est assignΓ©
1034 label_user_mail_option_only_assigned: Seulement pour ce qui m'est assignΓ©
1031 label_user_mail_option_none: Aucune notification
1035 label_user_mail_option_none: Aucune notification
1032 field_member_of_group: Groupe de l'assignΓ©
1036 field_member_of_group: Groupe de l'assignΓ©
1033 field_assigned_to_role: RΓ΄le de l'assignΓ©
1037 field_assigned_to_role: RΓ΄le de l'assignΓ©
1034 setting_emails_header: En-tΓͺte des emails
1038 setting_emails_header: En-tΓͺte des emails
1035 label_bulk_edit_selected_time_entries: Modifier les temps passΓ©s sΓ©lectionnΓ©s
1039 label_bulk_edit_selected_time_entries: Modifier les temps passΓ©s sΓ©lectionnΓ©s
1036 text_time_entries_destroy_confirmation: "Etes-vous sΓ»r de vouloir supprimer les temps passΓ©s sΓ©lectionnΓ©s ?"
1040 text_time_entries_destroy_confirmation: "Etes-vous sΓ»r de vouloir supprimer les temps passΓ©s sΓ©lectionnΓ©s ?"
1037 field_scm_path_encoding: Encodage des chemins
1041 field_scm_path_encoding: Encodage des chemins
1038 text_scm_path_encoding_note: "DΓ©faut : UTF-8"
1042 text_scm_path_encoding_note: "DΓ©faut : UTF-8"
1039 field_path_to_repository: Chemin du dΓ©pΓ΄t
1043 field_path_to_repository: Chemin du dΓ©pΓ΄t
1040 field_root_directory: RΓ©pertoire racine
1044 field_root_directory: RΓ©pertoire racine
1041 field_cvs_module: Module
1045 field_cvs_module: Module
1042 field_cvsroot: CVSROOT
1046 field_cvsroot: CVSROOT
1043 text_mercurial_repository_note: "DΓ©pΓ΄t local (exemples : /hgrepo, c:\\hgrepo)"
1047 text_mercurial_repository_note: "DΓ©pΓ΄t local (exemples : /hgrepo, c:\\hgrepo)"
1044 text_scm_command: Commande
1048 text_scm_command: Commande
1045 text_scm_command_version: Version
1049 text_scm_command_version: Version
1046 label_git_report_last_commit: Afficher le dernier commit des fichiers et rΓ©pertoires
1050 label_git_report_last_commit: Afficher le dernier commit des fichiers et rΓ©pertoires
1047 text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification.
1051 text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification.
1048 text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration.
1052 text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration.
1049 label_diff: diff
1053 label_diff: diff
1050 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
1054 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
1051 description_query_sort_criteria_direction: Ordre de tri
1055 description_query_sort_criteria_direction: Ordre de tri
1052 description_project_scope: Périmètre de recherche
1056 description_project_scope: Périmètre de recherche
1053 description_filter: Filtre
1057 description_filter: Filtre
1054 description_user_mail_notification: Option de notification
1058 description_user_mail_notification: Option de notification
1055 description_date_from: Date de dΓ©but
1059 description_date_from: Date de dΓ©but
1056 description_message_content: Contenu du message
1060 description_message_content: Contenu du message
1057 description_available_columns: Colonnes disponibles
1061 description_available_columns: Colonnes disponibles
1058 description_all_columns: Toutes les colonnes
1062 description_all_columns: Toutes les colonnes
1059 description_date_range_interval: Choisir une pΓ©riode
1063 description_date_range_interval: Choisir une pΓ©riode
1060 description_issue_category_reassign: Choisir une catΓ©gorie
1064 description_issue_category_reassign: Choisir une catΓ©gorie
1061 description_search: Champ de recherche
1065 description_search: Champ de recherche
1062 description_notes: Notes
1066 description_notes: Notes
1063 description_date_range_list: Choisir une pΓ©riode prΓ©dΓ©finie
1067 description_date_range_list: Choisir une pΓ©riode prΓ©dΓ©finie
1064 description_choose_project: Projets
1068 description_choose_project: Projets
1065 description_date_to: Date de fin
1069 description_date_to: Date de fin
1066 description_query_sort_criteria_attribute: Critère de tri
1070 description_query_sort_criteria_attribute: Critère de tri
1067 description_wiki_subpages_reassign: Choisir une nouvelle page parent
1071 description_wiki_subpages_reassign: Choisir une nouvelle page parent
1068 description_selected_columns: Colonnes sΓ©lectionnΓ©es
1072 description_selected_columns: Colonnes sΓ©lectionnΓ©es
1069 label_parent_revision: Parent
1073 label_parent_revision: Parent
1070 label_child_revision: Enfant
1074 label_child_revision: Enfant
1071 error_scm_annotate_big_text_file: Cette entrΓ©e ne peut pas Γͺtre annotΓ©e car elle excΓ¨de la taille maximale.
1075 error_scm_annotate_big_text_file: Cette entrΓ©e ne peut pas Γͺtre annotΓ©e car elle excΓ¨de la taille maximale.
1072 setting_repositories_encodings: Encodages des fichiers et des dΓ©pΓ΄ts
1076 setting_repositories_encodings: Encodages des fichiers et des dΓ©pΓ΄ts
1073 label_search_for_watchers: Rechercher des observateurs
1077 label_search_for_watchers: Rechercher des observateurs
1074 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Γ©.'
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 /* Redmine - project management software
1 /* Redmine - project management software
2 Copyright (C) 2006-2012 Jean-Philippe Lang */
2 Copyright (C) 2006-2012 Jean-Philippe Lang */
3
3
4 function checkAll(id, checked) {
4 function checkAll(id, checked) {
5 if (checked) {
5 if (checked) {
6 $('#'+id).find('input[type=checkbox]').attr('checked', true);
6 $('#'+id).find('input[type=checkbox]').attr('checked', true);
7 } else {
7 } else {
8 $('#'+id).find('input[type=checkbox]').removeAttr('checked');
8 $('#'+id).find('input[type=checkbox]').removeAttr('checked');
9 }
9 }
10 }
10 }
11
11
12 function toggleCheckboxesBySelector(selector) {
12 function toggleCheckboxesBySelector(selector) {
13 var all_checked = true;
13 var all_checked = true;
14 $(selector).each(function(index) {
14 $(selector).each(function(index) {
15 if (!$(this).is(':checked')) { all_checked = false; }
15 if (!$(this).is(':checked')) { all_checked = false; }
16 });
16 });
17 $(selector).attr('checked', !all_checked)
17 $(selector).attr('checked', !all_checked)
18 }
18 }
19
19
20 function showAndScrollTo(id, focus) {
20 function showAndScrollTo(id, focus) {
21 $('#'+id).show();
21 $('#'+id).show();
22 if (focus!=null) {
22 if (focus!=null) {
23 $('#'+focus).focus();
23 $('#'+focus).focus();
24 }
24 }
25 $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
25 $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
26 }
26 }
27
27
28 function toggleRowGroup(el) {
28 function toggleRowGroup(el) {
29 var tr = $(el).parents('tr').first();
29 var tr = $(el).parents('tr').first();
30 var n = tr.next();
30 var n = tr.next();
31 tr.toggleClass('open');
31 tr.toggleClass('open');
32 while (n.length && !n.hasClass('group')) {
32 while (n.length && !n.hasClass('group')) {
33 n.toggle();
33 n.toggle();
34 n = n.next('tr');
34 n = n.next('tr');
35 }
35 }
36 }
36 }
37
37
38 function collapseAllRowGroups(el) {
38 function collapseAllRowGroups(el) {
39 var tbody = $(el).parents('tbody').first();
39 var tbody = $(el).parents('tbody').first();
40 tbody.children('tr').each(function(index) {
40 tbody.children('tr').each(function(index) {
41 if ($(this).hasClass('group')) {
41 if ($(this).hasClass('group')) {
42 $(this).removeClass('open');
42 $(this).removeClass('open');
43 } else {
43 } else {
44 $(this).hide();
44 $(this).hide();
45 }
45 }
46 });
46 });
47 }
47 }
48
48
49 function expandAllRowGroups(el) {
49 function expandAllRowGroups(el) {
50 var tbody = $(el).parents('tbody').first();
50 var tbody = $(el).parents('tbody').first();
51 tbody.children('tr').each(function(index) {
51 tbody.children('tr').each(function(index) {
52 if ($(this).hasClass('group')) {
52 if ($(this).hasClass('group')) {
53 $(this).addClass('open');
53 $(this).addClass('open');
54 } else {
54 } else {
55 $(this).show();
55 $(this).show();
56 }
56 }
57 });
57 });
58 }
58 }
59
59
60 function toggleAllRowGroups(el) {
60 function toggleAllRowGroups(el) {
61 var tr = $(el).parents('tr').first();
61 var tr = $(el).parents('tr').first();
62 if (tr.hasClass('open')) {
62 if (tr.hasClass('open')) {
63 collapseAllRowGroups(el);
63 collapseAllRowGroups(el);
64 } else {
64 } else {
65 expandAllRowGroups(el);
65 expandAllRowGroups(el);
66 }
66 }
67 }
67 }
68
68
69 function toggleFieldset(el) {
69 function toggleFieldset(el) {
70 var fieldset = $(el).parents('fieldset').first();
70 var fieldset = $(el).parents('fieldset').first();
71 fieldset.toggleClass('collapsed');
71 fieldset.toggleClass('collapsed');
72 fieldset.children('div').toggle();
72 fieldset.children('div').toggle();
73 }
73 }
74
74
75 function hideFieldset(el) {
75 function hideFieldset(el) {
76 var fieldset = $(el).parents('fieldset').first();
76 var fieldset = $(el).parents('fieldset').first();
77 fieldset.toggleClass('collapsed');
77 fieldset.toggleClass('collapsed');
78 fieldset.children('div').hide();
78 fieldset.children('div').hide();
79 }
79 }
80
80
81 function initFilters(){
81 function initFilters(){
82 $('#add_filter_select').change(function(){
82 $('#add_filter_select').change(function(){
83 addFilter($(this).val(), '', []);
83 addFilter($(this).val(), '', []);
84 });
84 });
85 $('#filters-table td.field input[type=checkbox]').each(function(){
85 $('#filters-table td.field input[type=checkbox]').each(function(){
86 toggleFilter($(this).val());
86 toggleFilter($(this).val());
87 });
87 });
88 $('#filters-table td.field input[type=checkbox]').live('click',function(){
88 $('#filters-table td.field input[type=checkbox]').live('click',function(){
89 toggleFilter($(this).val());
89 toggleFilter($(this).val());
90 });
90 });
91 $('#filters-table .toggle-multiselect').live('click',function(){
91 $('#filters-table .toggle-multiselect').live('click',function(){
92 toggleMultiSelect($(this).siblings('select'));
92 toggleMultiSelect($(this).siblings('select'));
93 });
93 });
94 $('#filters-table input[type=text]').live('keypress', function(e){
94 $('#filters-table input[type=text]').live('keypress', function(e){
95 if (e.keyCode == 13) submit_query_form("query_form");
95 if (e.keyCode == 13) submit_query_form("query_form");
96 });
96 });
97 }
97 }
98
98
99 function addFilter(field, operator, values) {
99 function addFilter(field, operator, values) {
100 var fieldId = field.replace('.', '_');
100 var fieldId = field.replace('.', '_');
101 var tr = $('#tr_'+fieldId);
101 var tr = $('#tr_'+fieldId);
102 if (tr.length > 0) {
102 if (tr.length > 0) {
103 tr.show();
103 tr.show();
104 } else {
104 } else {
105 buildFilterRow(field, operator, values);
105 buildFilterRow(field, operator, values);
106 }
106 }
107 $('#cb_'+fieldId).attr('checked', true);
107 $('#cb_'+fieldId).attr('checked', true);
108 toggleFilter(field);
108 toggleFilter(field);
109 $('#add_filter_select').val('').children('option').each(function(){
109 $('#add_filter_select').val('').children('option').each(function(){
110 if ($(this).attr('value') == field) {
110 if ($(this).attr('value') == field) {
111 $(this).attr('disabled', true);
111 $(this).attr('disabled', true);
112 }
112 }
113 });
113 });
114 }
114 }
115
115
116 function buildFilterRow(field, operator, values) {
116 function buildFilterRow(field, operator, values) {
117 var fieldId = field.replace('.', '_');
117 var fieldId = field.replace('.', '_');
118 var filterTable = $("#filters-table");
118 var filterTable = $("#filters-table");
119 var filterOptions = availableFilters[field];
119 var filterOptions = availableFilters[field];
120 var operators = operatorByType[filterOptions['type']];
120 var operators = operatorByType[filterOptions['type']];
121 var filterValues = filterOptions['values'];
121 var filterValues = filterOptions['values'];
122 var i, select;
122 var i, select;
123
123
124 var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html(
124 var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html(
125 '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' +
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 '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' +
126 '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' +
127 '<td class="values"></td>'
127 '<td class="values"></td>'
128 );
128 );
129 filterTable.append(tr);
129 filterTable.append(tr);
130
130
131 select = tr.find('td.operator select');
131 select = tr.find('td.operator select');
132 for (i=0;i<operators.length;i++){
132 for (i=0;i<operators.length;i++){
133 var option = $('<option>').val(operators[i]).html(operatorLabels[operators[i]]);
133 var option = $('<option>').val(operators[i]).html(operatorLabels[operators[i]]);
134 if (operators[i] == operator) {option.attr('selected', true)};
134 if (operators[i] == operator) {option.attr('selected', true)};
135 select.append(option);
135 select.append(option);
136 }
136 }
137 select.change(function(){toggleOperator(field)});
137 select.change(function(){toggleOperator(field)});
138
138
139 switch (filterOptions['type']){
139 switch (filterOptions['type']){
140 case "list":
140 case "list":
141 case "list_optional":
141 case "list_optional":
142 case "list_status":
142 case "list_status":
143 case "list_subprojects":
143 case "list_subprojects":
144 tr.find('td.values').append(
144 tr.find('td.values').append(
145 '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
145 '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
146 ' <span class="toggle-multiselect">&nbsp;</span></span>'
146 ' <span class="toggle-multiselect">&nbsp;</span></span>'
147 );
147 );
148 select = tr.find('td.values select');
148 select = tr.find('td.values select');
149 if (values.length > 1) {select.attr('multiple', true)};
149 if (values.length > 1) {select.attr('multiple', true)};
150 for (i=0;i<filterValues.length;i++){
150 for (i=0;i<filterValues.length;i++){
151 var filterValue = filterValues[i];
151 var filterValue = filterValues[i];
152 var option = $('<option>');
152 var option = $('<option>');
153 if ($.isArray(filterValue)) {
153 if ($.isArray(filterValue)) {
154 option.val(filterValue[1]).html(filterValue[0]);
154 option.val(filterValue[1]).html(filterValue[0]);
155 if (values.indexOf(filterValue[1]) > -1) {option.attr('selected', true)};
155 } else {
156 } else {
156 option.val(filterValue).html(filterValue);
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 select.append(option);
160 select.append(option);
160 }
161 }
161 break;
162 break;
162 case "date":
163 case "date":
163 case "date_past":
164 case "date_past":
164 tr.find('td.values').append(
165 tr.find('td.values').append(
165 '<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 '<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 ' <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 ' <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 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" value="'+values[0]+'" /> '+labelDayPlural+'</span>'
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 $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions);
170 $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions);
170 $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions);
171 $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions);
171 $('#values_'+fieldId).val(values[0]);
172 $('#values_'+fieldId).val(values[0]);
172 break;
173 break;
173 case "string":
174 case "string":
174 case "text":
175 case "text":
175 tr.find('td.values').append(
176 tr.find('td.values').append(
176 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" value="'+values[0]+'" /></span>'
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 $('#values_'+fieldId).val(values[0]);
179 $('#values_'+fieldId).val(values[0]);
179 break;
180 break;
180 case "integer":
181 case "integer":
181 case "float":
182 case "float":
182 tr.find('td.values').append(
183 tr.find('td.values').append(
183 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" value="'+values[0]+'" /></span>' +
184 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" value="'+values[0]+'" /></span>' +
184 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" value="'+values[1]+'" /></span>'
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 $('#values_'+fieldId+'_1').val(values[0]);
187 $('#values_'+fieldId+'_1').val(values[0]);
187 $('#values_'+fieldId+'_2').val(values[1]);
188 $('#values_'+fieldId+'_2').val(values[1]);
188 break;
189 break;
189 }
190 }
190 }
191 }
191
192
192 function toggleFilter(field) {
193 function toggleFilter(field) {
193 var fieldId = field.replace('.', '_');
194 var fieldId = field.replace('.', '_');
194 if ($('#cb_' + fieldId).is(':checked')) {
195 if ($('#cb_' + fieldId).is(':checked')) {
195 $("#operators_" + fieldId).show().removeAttr('disabled');
196 $("#operators_" + fieldId).show().removeAttr('disabled');
196 toggleOperator(field);
197 toggleOperator(field);
197 } else {
198 } else {
198 $("#operators_" + fieldId).hide().attr('disabled', true);
199 $("#operators_" + fieldId).hide().attr('disabled', true);
199 enableValues(field, []);
200 enableValues(field, []);
200 }
201 }
201 }
202 }
202
203
203 function enableValues(field, indexes) {
204 function enableValues(field, indexes) {
204 var fieldId = field.replace('.', '_');
205 var fieldId = field.replace('.', '_');
205 $('#tr_'+fieldId+' td.values .value').each(function(index) {
206 $('#tr_'+fieldId+' td.values .value').each(function(index) {
206 if (indexes.indexOf(index) >= 0) {
207 if (indexes.indexOf(index) >= 0) {
207 $(this).removeAttr('disabled');
208 $(this).removeAttr('disabled');
208 $(this).parents('span').first().show();
209 $(this).parents('span').first().show();
209 } else {
210 } else {
210 $(this).val('');
211 $(this).val('');
211 $(this).attr('disabled', true);
212 $(this).attr('disabled', true);
212 $(this).parents('span').first().hide();
213 $(this).parents('span').first().hide();
213 }
214 }
214
215
215 if ($(this).hasClass('group')) {
216 if ($(this).hasClass('group')) {
216 $(this).addClass('open');
217 $(this).addClass('open');
217 } else {
218 } else {
218 $(this).show();
219 $(this).show();
219 }
220 }
220 });
221 });
221 }
222 }
222
223
223 function toggleOperator(field) {
224 function toggleOperator(field) {
224 var fieldId = field.replace('.', '_');
225 var fieldId = field.replace('.', '_');
225 var operator = $("#operators_" + fieldId);
226 var operator = $("#operators_" + fieldId);
226 switch (operator.val()) {
227 switch (operator.val()) {
227 case "!*":
228 case "!*":
228 case "*":
229 case "*":
229 case "t":
230 case "t":
230 case "w":
231 case "w":
231 case "o":
232 case "o":
232 case "c":
233 case "c":
233 enableValues(field, []);
234 enableValues(field, []);
234 break;
235 break;
235 case "><":
236 case "><":
236 enableValues(field, [0,1]);
237 enableValues(field, [0,1]);
237 break;
238 break;
238 case "<t+":
239 case "<t+":
239 case ">t+":
240 case ">t+":
240 case "t+":
241 case "t+":
241 case ">t-":
242 case ">t-":
242 case "<t-":
243 case "<t-":
243 case "t-":
244 case "t-":
244 enableValues(field, [2]);
245 enableValues(field, [2]);
245 break;
246 break;
246 default:
247 default:
247 enableValues(field, [0]);
248 enableValues(field, [0]);
248 break;
249 break;
249 }
250 }
250 }
251 }
251
252
252 function toggleMultiSelect(el) {
253 function toggleMultiSelect(el) {
253 if (el.attr('multiple')) {
254 if (el.attr('multiple')) {
254 el.removeAttr('multiple');
255 el.removeAttr('multiple');
255 } else {
256 } else {
256 el.attr('multiple', true);
257 el.attr('multiple', true);
257 }
258 }
258 }
259 }
259
260
260 function submit_query_form(id) {
261 function submit_query_form(id) {
261 selectAllOptions("selected_columns");
262 selectAllOptions("selected_columns");
262 $('#'+id).submit();
263 $('#'+id).submit();
263 }
264 }
264
265
265 var fileFieldCount = 1;
266 var fileFieldCount = 1;
266 function addFileField() {
267 function addFileField() {
267 var fields = $('#attachments_fields');
268 var fields = $('#attachments_fields');
268 if (fields.children().length >= 10) return false;
269 if (fields.children().length >= 10) return false;
269 fileFieldCount++;
270 fileFieldCount++;
270 var s = fields.children('span').first().clone();
271 var s = fields.children('span').first().clone();
271 s.children('input.file').attr('name', "attachments[" + fileFieldCount + "][file]").val('');
272 s.children('input.file').attr('name', "attachments[" + fileFieldCount + "][file]").val('');
272 s.children('input.description').attr('name', "attachments[" + fileFieldCount + "][description]").val('');
273 s.children('input.description').attr('name', "attachments[" + fileFieldCount + "][description]").val('');
273 fields.append(s);
274 fields.append(s);
274 }
275 }
275
276
276 function removeFileField(el) {
277 function removeFileField(el) {
277 var fields = $('#attachments_fields');
278 var fields = $('#attachments_fields');
278 var s = $(el).parents('span').first();
279 var s = $(el).parents('span').first();
279 if (fields.children().length > 1) {
280 if (fields.children().length > 1) {
280 s.remove();
281 s.remove();
281 } else {
282 } else {
282 s.children('input.file').val('');
283 s.children('input.file').val('');
283 s.children('input.description').val('');
284 s.children('input.description').val('');
284 }
285 }
285 }
286 }
286
287
287 function checkFileSize(el, maxSize, message) {
288 function checkFileSize(el, maxSize, message) {
288 var files = el.files;
289 var files = el.files;
289 if (files) {
290 if (files) {
290 for (var i=0; i<files.length; i++) {
291 for (var i=0; i<files.length; i++) {
291 if (files[i].size > maxSize) {
292 if (files[i].size > maxSize) {
292 alert(message);
293 alert(message);
293 el.value = "";
294 el.value = "";
294 }
295 }
295 }
296 }
296 }
297 }
297 }
298 }
298
299
299 function showTab(name) {
300 function showTab(name) {
300 $('div#content .tab-content').hide();
301 $('div#content .tab-content').hide();
301 $('div.tabs a').removeClass('selected');
302 $('div.tabs a').removeClass('selected');
302 $('#tab-content-' + name).show();
303 $('#tab-content-' + name).show();
303 $('#tab-' + name).addClass('selected');
304 $('#tab-' + name).addClass('selected');
304 return false;
305 return false;
305 }
306 }
306
307
307 function moveTabRight(el) {
308 function moveTabRight(el) {
308 var lis = $(el).parents('div.tabs').first().find('ul').children();
309 var lis = $(el).parents('div.tabs').first().find('ul').children();
309 var tabsWidth = 0;
310 var tabsWidth = 0;
310 var i = 0;
311 var i = 0;
311 lis.each(function(){
312 lis.each(function(){
312 if ($(this).is(':visible')) {
313 if ($(this).is(':visible')) {
313 tabsWidth += $(this).width() + 6;
314 tabsWidth += $(this).width() + 6;
314 }
315 }
315 });
316 });
316 if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; }
317 if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; }
317 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
318 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
318 lis.eq(i).hide();
319 lis.eq(i).hide();
319 }
320 }
320
321
321 function moveTabLeft(el) {
322 function moveTabLeft(el) {
322 var lis = $(el).parents('div.tabs').first().find('ul').children();
323 var lis = $(el).parents('div.tabs').first().find('ul').children();
323 var i = 0;
324 var i = 0;
324 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
325 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
325 if (i>0) {
326 if (i>0) {
326 lis.eq(i-1).show();
327 lis.eq(i-1).show();
327 }
328 }
328 }
329 }
329
330
330 function displayTabsButtons() {
331 function displayTabsButtons() {
331 var lis;
332 var lis;
332 var tabsWidth = 0;
333 var tabsWidth = 0;
333 var el;
334 var el;
334 $('div.tabs').each(function() {
335 $('div.tabs').each(function() {
335 el = $(this);
336 el = $(this);
336 lis = el.find('ul').children();
337 lis = el.find('ul').children();
337 lis.each(function(){
338 lis.each(function(){
338 if ($(this).is(':visible')) {
339 if ($(this).is(':visible')) {
339 tabsWidth += $(this).width() + 6;
340 tabsWidth += $(this).width() + 6;
340 }
341 }
341 });
342 });
342 if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) {
343 if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) {
343 el.find('div.tabs-buttons').hide();
344 el.find('div.tabs-buttons').hide();
344 } else {
345 } else {
345 el.find('div.tabs-buttons').show();
346 el.find('div.tabs-buttons').show();
346 }
347 }
347 });
348 });
348 }
349 }
349
350
350 function setPredecessorFieldsVisibility() {
351 function setPredecessorFieldsVisibility() {
351 var relationType = $('#relation_relation_type');
352 var relationType = $('#relation_relation_type');
352 if (relationType.val() == "precedes" || relationType.val() == "follows") {
353 if (relationType.val() == "precedes" || relationType.val() == "follows") {
353 $('#predecessor_fields').show();
354 $('#predecessor_fields').show();
354 } else {
355 } else {
355 $('#predecessor_fields').hide();
356 $('#predecessor_fields').hide();
356 }
357 }
357 }
358 }
358
359
359 function showModal(id, width) {
360 function showModal(id, width) {
360 var el = $('#'+id).first();
361 var el = $('#'+id).first();
361 if (el.length == 0 || el.is(':visible')) {return;}
362 if (el.length == 0 || el.is(':visible')) {return;}
362 var title = el.find('h3.title').text();
363 var title = el.find('h3.title').text();
363 el.dialog({
364 el.dialog({
364 width: width,
365 width: width,
365 modal: true,
366 modal: true,
366 resizable: false,
367 resizable: false,
367 dialogClass: 'modal',
368 dialogClass: 'modal',
368 title: title
369 title: title
369 });
370 });
370 el.find("input[type=text], input[type=submit]").first().focus();
371 el.find("input[type=text], input[type=submit]").first().focus();
371 }
372 }
372
373
373 function hideModal(el) {
374 function hideModal(el) {
374 var modal;
375 var modal;
375 if (el) {
376 if (el) {
376 modal = $(el).parents('.ui-dialog-content');
377 modal = $(el).parents('.ui-dialog-content');
377 } else {
378 } else {
378 modal = $('#ajax-modal');
379 modal = $('#ajax-modal');
379 }
380 }
380 modal.dialog("close");
381 modal.dialog("close");
381 }
382 }
382
383
383 function submitPreview(url, form, target) {
384 function submitPreview(url, form, target) {
384 $.ajax({
385 $.ajax({
385 url: url,
386 url: url,
386 type: 'post',
387 type: 'post',
387 data: $('#'+form).serialize(),
388 data: $('#'+form).serialize(),
388 success: function(data){
389 success: function(data){
389 $('#'+target).html(data);
390 $('#'+target).html(data);
390 }
391 }
391 });
392 });
392 }
393 }
393
394
394 function collapseScmEntry(id) {
395 function collapseScmEntry(id) {
395 $('.'+id).each(function() {
396 $('.'+id).each(function() {
396 if ($(this).hasClass('open')) {
397 if ($(this).hasClass('open')) {
397 collapseScmEntry($(this).attr('id'));
398 collapseScmEntry($(this).attr('id'));
398 }
399 }
399 $(this).hide();
400 $(this).hide();
400 });
401 });
401 $('#'+id).removeClass('open');
402 $('#'+id).removeClass('open');
402 }
403 }
403
404
404 function expandScmEntry(id) {
405 function expandScmEntry(id) {
405 $('.'+id).each(function() {
406 $('.'+id).each(function() {
406 $(this).show();
407 $(this).show();
407 if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
408 if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
408 expandScmEntry($(this).attr('id'));
409 expandScmEntry($(this).attr('id'));
409 }
410 }
410 });
411 });
411 $('#'+id).addClass('open');
412 $('#'+id).addClass('open');
412 }
413 }
413
414
414 function scmEntryClick(id, url) {
415 function scmEntryClick(id, url) {
415 el = $('#'+id);
416 el = $('#'+id);
416 if (el.hasClass('open')) {
417 if (el.hasClass('open')) {
417 collapseScmEntry(id);
418 collapseScmEntry(id);
418 el.addClass('collapsed');
419 el.addClass('collapsed');
419 return false;
420 return false;
420 } else if (el.hasClass('loaded')) {
421 } else if (el.hasClass('loaded')) {
421 expandScmEntry(id);
422 expandScmEntry(id);
422 el.removeClass('collapsed');
423 el.removeClass('collapsed');
423 return false;
424 return false;
424 }
425 }
425 if (el.hasClass('loading')) {
426 if (el.hasClass('loading')) {
426 return false;
427 return false;
427 }
428 }
428 el.addClass('loading');
429 el.addClass('loading');
429 $.ajax({
430 $.ajax({
430 url: url,
431 url: url,
431 success: function(data){
432 success: function(data){
432 el.after(data);
433 el.after(data);
433 el.addClass('open').addClass('loaded').removeClass('loading');
434 el.addClass('open').addClass('loaded').removeClass('loading');
434 }
435 }
435 });
436 });
436 return true;
437 return true;
437 }
438 }
438
439
439 function randomKey(size) {
440 function randomKey(size) {
440 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 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 var key = '';
442 var key = '';
442 for (i = 0; i < size; i++) {
443 for (i = 0; i < size; i++) {
443 key += chars[Math.floor(Math.random() * chars.length)];
444 key += chars[Math.floor(Math.random() * chars.length)];
444 }
445 }
445 return key;
446 return key;
446 }
447 }
447
448
448 // Can't use Rails' remote select because we need the form data
449 // Can't use Rails' remote select because we need the form data
449 function updateIssueFrom(url) {
450 function updateIssueFrom(url) {
450 $.ajax({
451 $.ajax({
451 url: url,
452 url: url,
452 type: 'post',
453 type: 'post',
453 data: $('#issue-form').serialize()
454 data: $('#issue-form').serialize()
454 });
455 });
455 }
456 }
456
457
457 function updateBulkEditFrom(url) {
458 function updateBulkEditFrom(url) {
458 $.ajax({
459 $.ajax({
459 url: url,
460 url: url,
460 type: 'post',
461 type: 'post',
461 data: $('#bulk_edit_form').serialize()
462 data: $('#bulk_edit_form').serialize()
462 });
463 });
463 }
464 }
464
465
465 function observeAutocompleteField(fieldId, url) {
466 function observeAutocompleteField(fieldId, url) {
466 $('#'+fieldId).autocomplete({
467 $('#'+fieldId).autocomplete({
467 source: url,
468 source: url,
468 minLength: 2,
469 minLength: 2,
469 });
470 });
470 }
471 }
471
472
472 function observeSearchfield(fieldId, targetId, url) {
473 function observeSearchfield(fieldId, targetId, url) {
473 $('#'+fieldId).each(function() {
474 $('#'+fieldId).each(function() {
474 var $this = $(this);
475 var $this = $(this);
475 $this.attr('data-value-was', $this.val());
476 $this.attr('data-value-was', $this.val());
476 var check = function() {
477 var check = function() {
477 var val = $this.val();
478 var val = $this.val();
478 if ($this.attr('data-value-was') != val){
479 if ($this.attr('data-value-was') != val){
479 $this.attr('data-value-was', val);
480 $this.attr('data-value-was', val);
480 if (val != '') {
481 if (val != '') {
481 $.ajax({
482 $.ajax({
482 url: url,
483 url: url,
483 type: 'get',
484 type: 'get',
484 data: {q: $this.val()},
485 data: {q: $this.val()},
485 success: function(data){ $('#'+targetId).html(data); },
486 success: function(data){ $('#'+targetId).html(data); },
486 beforeSend: function(){ $this.addClass('ajax-loading'); },
487 beforeSend: function(){ $this.addClass('ajax-loading'); },
487 complete: function(){ $this.removeClass('ajax-loading'); }
488 complete: function(){ $this.removeClass('ajax-loading'); }
488 });
489 });
489 }
490 }
490 }
491 }
491 };
492 };
492 var reset = function() {
493 var reset = function() {
493 if (timer) {
494 if (timer) {
494 clearInterval(timer);
495 clearInterval(timer);
495 timer = setInterval(check, 300);
496 timer = setInterval(check, 300);
496 }
497 }
497 };
498 };
498 var timer = setInterval(check, 300);
499 var timer = setInterval(check, 300);
499 $this.bind('keyup click mousemove', reset);
500 $this.bind('keyup click mousemove', reset);
500 });
501 });
501 }
502 }
502
503
503 function observeProjectModules() {
504 function observeProjectModules() {
504 var f = function() {
505 var f = function() {
505 /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */
506 /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */
506 if ($('#project_enabled_module_names_issue_tracking').attr('checked')) {
507 if ($('#project_enabled_module_names_issue_tracking').attr('checked')) {
507 $('#project_trackers').show();
508 $('#project_trackers').show();
508 }else{
509 }else{
509 $('#project_trackers').hide();
510 $('#project_trackers').hide();
510 }
511 }
511 };
512 };
512
513
513 $(window).load(f);
514 $(window).load(f);
514 $('#project_enabled_module_names_issue_tracking').change(f);
515 $('#project_enabled_module_names_issue_tracking').change(f);
515 }
516 }
516
517
517 function initMyPageSortable(list, url) {
518 function initMyPageSortable(list, url) {
518 $('#list-'+list).sortable({
519 $('#list-'+list).sortable({
519 connectWith: '.block-receiver',
520 connectWith: '.block-receiver',
520 tolerance: 'pointer',
521 tolerance: 'pointer',
521 update: function(){
522 update: function(){
522 $.ajax({
523 $.ajax({
523 url: url,
524 url: url,
524 type: 'post',
525 type: 'post',
525 data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})}
526 data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})}
526 });
527 });
527 }
528 }
528 });
529 });
529 $("#list-top, #list-left, #list-right").disableSelection();
530 $("#list-top, #list-left, #list-right").disableSelection();
530 }
531 }
531
532
532 var warnLeavingUnsavedMessage;
533 var warnLeavingUnsavedMessage;
533 function warnLeavingUnsaved(message) {
534 function warnLeavingUnsaved(message) {
534 warnLeavingUnsavedMessage = message;
535 warnLeavingUnsavedMessage = message;
535
536
536 $('form').submit(function(){
537 $('form').submit(function(){
537 $('textarea').removeData('changed');
538 $('textarea').removeData('changed');
538 });
539 });
539 $('textarea').change(function(){
540 $('textarea').change(function(){
540 $(this).data('changed', 'changed');
541 $(this).data('changed', 'changed');
541 });
542 });
542 window.onbeforeunload = function(){
543 window.onbeforeunload = function(){
543 var warn = false;
544 var warn = false;
544 $('textarea').blur().each(function(){
545 $('textarea').blur().each(function(){
545 if ($(this).data('changed')) {
546 if ($(this).data('changed')) {
546 warn = true;
547 warn = true;
547 }
548 }
548 });
549 });
549 if (warn) {return warnLeavingUnsavedMessage;}
550 if (warn) {return warnLeavingUnsavedMessage;}
550 };
551 };
551 };
552 };
552
553
553 $(document).ready(function(){
554 $(document).ready(function(){
554 $('#ajax-indicator').bind('ajaxSend', function(){
555 $('#ajax-indicator').bind('ajaxSend', function(){
555 if ($('.ajax-loading').length == 0) {
556 if ($('.ajax-loading').length == 0) {
556 $('#ajax-indicator').show();
557 $('#ajax-indicator').show();
557 }
558 }
558 });
559 });
559 $('#ajax-indicator').bind('ajaxStop', function(){
560 $('#ajax-indicator').bind('ajaxStop', function(){
560 $('#ajax-indicator').hide();
561 $('#ajax-indicator').hide();
561 });
562 });
562 });
563 });
563
564
564 function hideOnLoad() {
565 function hideOnLoad() {
565 $('.hol').hide();
566 $('.hol').hide();
566 }
567 }
567
568
568 function addFormObserversForDoubleSubmit() {
569 function addFormObserversForDoubleSubmit() {
569 $('form[method=post]').each(function() {
570 $('form[method=post]').each(function() {
570 if (!$(this).hasClass('multiple-submit')) {
571 if (!$(this).hasClass('multiple-submit')) {
571 $(this).submit(function(form_submission) {
572 $(this).submit(function(form_submission) {
572 if ($(form_submission.target).attr('data-submitted')) {
573 if ($(form_submission.target).attr('data-submitted')) {
573 form_submission.preventDefault();
574 form_submission.preventDefault();
574 } else {
575 } else {
575 $(form_submission.target).attr('data-submitted', true);
576 $(form_submission.target).attr('data-submitted', true);
576 }
577 }
577 });
578 });
578 }
579 }
579 });
580 });
580 }
581 }
581
582
582 $(document).ready(hideOnLoad);
583 $(document).ready(hideOnLoad);
583 $(document).ready(addFormObserversForDoubleSubmit);
584 $(document).ready(addFormObserversForDoubleSubmit);
@@ -1,1125 +1,1125
1 html {overflow-y:scroll;}
1 html {overflow-y:scroll;}
2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
3
3
4 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
5 h1 {margin:0; padding:0; font-size: 24px;}
5 h1 {margin:0; padding:0; font-size: 24px;}
6 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; color: #444;}
6 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; color: #444;}
7 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; color: #444;}
7 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; color: #444;}
8 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
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 /***** Layout *****/
10 /***** Layout *****/
11 #wrapper {background: white;}
11 #wrapper {background: white;}
12
12
13 #top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
13 #top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
14 #top-menu ul {margin: 0; padding: 0;}
14 #top-menu ul {margin: 0; padding: 0;}
15 #top-menu li {
15 #top-menu li {
16 float:left;
16 float:left;
17 list-style-type:none;
17 list-style-type:none;
18 margin: 0px 0px 0px 0px;
18 margin: 0px 0px 0px 0px;
19 padding: 0px 0px 0px 0px;
19 padding: 0px 0px 0px 0px;
20 white-space:nowrap;
20 white-space:nowrap;
21 }
21 }
22 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
22 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
23 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
23 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
24
24
25 #account {float:right;}
25 #account {float:right;}
26
26
27 #header {height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
27 #header {height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
28 #header a {color:#f8f8f8;}
28 #header a {color:#f8f8f8;}
29 #header h1 a.ancestor { font-size: 80%; }
29 #header h1 a.ancestor { font-size: 80%; }
30 #quick-search {float:right;}
30 #quick-search {float:right;}
31
31
32 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
32 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
33 #main-menu ul {margin: 0; padding: 0;}
33 #main-menu ul {margin: 0; padding: 0;}
34 #main-menu li {
34 #main-menu li {
35 float:left;
35 float:left;
36 list-style-type:none;
36 list-style-type:none;
37 margin: 0px 2px 0px 0px;
37 margin: 0px 2px 0px 0px;
38 padding: 0px 0px 0px 0px;
38 padding: 0px 0px 0px 0px;
39 white-space:nowrap;
39 white-space:nowrap;
40 }
40 }
41 #main-menu li a {
41 #main-menu li a {
42 display: block;
42 display: block;
43 color: #fff;
43 color: #fff;
44 text-decoration: none;
44 text-decoration: none;
45 font-weight: bold;
45 font-weight: bold;
46 margin: 0;
46 margin: 0;
47 padding: 4px 10px 4px 10px;
47 padding: 4px 10px 4px 10px;
48 }
48 }
49 #main-menu li a:hover {background:#759FCF; color:#fff;}
49 #main-menu li a:hover {background:#759FCF; color:#fff;}
50 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
50 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
51
51
52 #admin-menu ul {margin: 0; padding: 0;}
52 #admin-menu ul {margin: 0; padding: 0;}
53 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
53 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
54
54
55 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
55 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
56 #admin-menu a.projects { background-image: url(../images/projects.png); }
56 #admin-menu a.projects { background-image: url(../images/projects.png); }
57 #admin-menu a.users { background-image: url(../images/user.png); }
57 #admin-menu a.users { background-image: url(../images/user.png); }
58 #admin-menu a.groups { background-image: url(../images/group.png); }
58 #admin-menu a.groups { background-image: url(../images/group.png); }
59 #admin-menu a.roles { background-image: url(../images/database_key.png); }
59 #admin-menu a.roles { background-image: url(../images/database_key.png); }
60 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
60 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
61 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
61 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
62 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
62 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
63 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
63 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
64 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
64 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
65 #admin-menu a.settings { background-image: url(../images/changeset.png); }
65 #admin-menu a.settings { background-image: url(../images/changeset.png); }
66 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
66 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
67 #admin-menu a.info { background-image: url(../images/help.png); }
67 #admin-menu a.info { background-image: url(../images/help.png); }
68 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
68 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
69
69
70 #main {background-color:#EEEEEE;}
70 #main {background-color:#EEEEEE;}
71
71
72 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
72 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
73 * html #sidebar{ width: 22%; }
73 * html #sidebar{ width: 22%; }
74 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
74 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
75 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
75 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
76 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
76 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
77 #sidebar .contextual { margin-right: 1em; }
77 #sidebar .contextual { margin-right: 1em; }
78
78
79 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
79 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
80 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
80 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
81 html>body #content { min-height: 600px; }
81 html>body #content { min-height: 600px; }
82 * html body #content { height: 600px; } /* IE */
82 * html body #content { height: 600px; } /* IE */
83
83
84 #main.nosidebar #sidebar{ display: none; }
84 #main.nosidebar #sidebar{ display: none; }
85 #main.nosidebar #content{ width: auto; border-right: 0; }
85 #main.nosidebar #content{ width: auto; border-right: 0; }
86
86
87 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
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 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
89 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
90 #login-form table td {padding: 6px;}
90 #login-form table td {padding: 6px;}
91 #login-form label {font-weight: bold;}
91 #login-form label {font-weight: bold;}
92 #login-form input#username, #login-form input#password { width: 300px; }
92 #login-form input#username, #login-form input#password { width: 300px; }
93
93
94 div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
94 div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
95 div.modal h3.title {display:none;}
95 div.modal h3.title {display:none;}
96 div.modal p.buttons {text-align:right; margin-bottom:0;}
96 div.modal p.buttons {text-align:right; margin-bottom:0;}
97
97
98 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
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 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
100 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
101
101
102 /***** Links *****/
102 /***** Links *****/
103 a, a:link, a:visited{ color: #169; text-decoration: none; }
103 a, a:link, a:visited{ color: #169; text-decoration: none; }
104 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
104 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
105 a img{ border: 0; }
105 a img{ border: 0; }
106
106
107 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
107 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
108 a.project.closed, a.project.closed:link, a.project.closed:visited { color: #999; }
108 a.project.closed, a.project.closed:link, a.project.closed:visited { color: #999; }
109
109
110 #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;}
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 #sidebar a.selected:hover {text-decoration:none;}
111 #sidebar a.selected:hover {text-decoration:none;}
112 #admin-menu a {line-height:1.7em;}
112 #admin-menu a {line-height:1.7em;}
113 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
113 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
114
114
115 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
115 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
116 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
116 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
117
117
118 a#toggle-completed-versions {color:#999;}
118 a#toggle-completed-versions {color:#999;}
119 /***** Tables *****/
119 /***** Tables *****/
120 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
120 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
121 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
121 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
122 table.list td { vertical-align: top; }
122 table.list td { vertical-align: top; }
123 table.list td.id { width: 2%; text-align: center;}
123 table.list td.id { width: 2%; text-align: center;}
124 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
124 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
125 table.list td.checkbox input {padding:0px;}
125 table.list td.checkbox input {padding:0px;}
126 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
126 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
127 table.list td.buttons a { padding-right: 0.6em; }
127 table.list td.buttons a { padding-right: 0.6em; }
128 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
128 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
129
129
130 tr.project td.name a { white-space:nowrap; }
130 tr.project td.name a { white-space:nowrap; }
131 tr.project.closed, tr.project.archived { color: #aaa; }
131 tr.project.closed, tr.project.archived { color: #aaa; }
132 tr.project.closed a, tr.project.archived a { color: #aaa; }
132 tr.project.closed a, tr.project.archived a { color: #aaa; }
133
133
134 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
134 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
135 tr.project.idnt-1 td.name {padding-left: 0.5em;}
135 tr.project.idnt-1 td.name {padding-left: 0.5em;}
136 tr.project.idnt-2 td.name {padding-left: 2em;}
136 tr.project.idnt-2 td.name {padding-left: 2em;}
137 tr.project.idnt-3 td.name {padding-left: 3.5em;}
137 tr.project.idnt-3 td.name {padding-left: 3.5em;}
138 tr.project.idnt-4 td.name {padding-left: 5em;}
138 tr.project.idnt-4 td.name {padding-left: 5em;}
139 tr.project.idnt-5 td.name {padding-left: 6.5em;}
139 tr.project.idnt-5 td.name {padding-left: 6.5em;}
140 tr.project.idnt-6 td.name {padding-left: 8em;}
140 tr.project.idnt-6 td.name {padding-left: 8em;}
141 tr.project.idnt-7 td.name {padding-left: 9.5em;}
141 tr.project.idnt-7 td.name {padding-left: 9.5em;}
142 tr.project.idnt-8 td.name {padding-left: 11em;}
142 tr.project.idnt-8 td.name {padding-left: 11em;}
143 tr.project.idnt-9 td.name {padding-left: 12.5em;}
143 tr.project.idnt-9 td.name {padding-left: 12.5em;}
144
144
145 tr.issue { text-align: center; white-space: nowrap; }
145 tr.issue { text-align: center; white-space: nowrap; }
146 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text { white-space: normal; }
146 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text { white-space: normal; }
147 tr.issue td.subject { text-align: left; }
147 tr.issue td.subject { text-align: left; }
148 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
148 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
149
149
150 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
150 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
151 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
151 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
152 tr.issue.idnt-2 td.subject {padding-left: 2em;}
152 tr.issue.idnt-2 td.subject {padding-left: 2em;}
153 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
153 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
154 tr.issue.idnt-4 td.subject {padding-left: 5em;}
154 tr.issue.idnt-4 td.subject {padding-left: 5em;}
155 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
155 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
156 tr.issue.idnt-6 td.subject {padding-left: 8em;}
156 tr.issue.idnt-6 td.subject {padding-left: 8em;}
157 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
157 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
158 tr.issue.idnt-8 td.subject {padding-left: 11em;}
158 tr.issue.idnt-8 td.subject {padding-left: 11em;}
159 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
159 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
160
160
161 tr.entry { border: 1px solid #f8f8f8; }
161 tr.entry { border: 1px solid #f8f8f8; }
162 tr.entry td { white-space: nowrap; }
162 tr.entry td { white-space: nowrap; }
163 tr.entry td.filename { width: 30%; }
163 tr.entry td.filename { width: 30%; }
164 tr.entry td.filename_no_report { width: 70%; }
164 tr.entry td.filename_no_report { width: 70%; }
165 tr.entry td.size { text-align: right; font-size: 90%; }
165 tr.entry td.size { text-align: right; font-size: 90%; }
166 tr.entry td.revision, tr.entry td.author { text-align: center; }
166 tr.entry td.revision, tr.entry td.author { text-align: center; }
167 tr.entry td.age { text-align: right; }
167 tr.entry td.age { text-align: right; }
168 tr.entry.file td.filename a { margin-left: 16px; }
168 tr.entry.file td.filename a { margin-left: 16px; }
169 tr.entry.file td.filename_no_report a { margin-left: 16px; }
169 tr.entry.file td.filename_no_report a { margin-left: 16px; }
170
170
171 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
171 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
172 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
172 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
173
173
174 tr.changeset { height: 20px }
174 tr.changeset { height: 20px }
175 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
175 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
176 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
176 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
177 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
177 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
178 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
178 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
179
179
180 table.files tr.file td { text-align: center; }
180 table.files tr.file td { text-align: center; }
181 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
181 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
182 table.files tr.file td.digest { font-size: 80%; }
182 table.files tr.file td.digest { font-size: 80%; }
183
183
184 table.members td.roles, table.memberships td.roles { width: 45%; }
184 table.members td.roles, table.memberships td.roles { width: 45%; }
185
185
186 tr.message { height: 2.6em; }
186 tr.message { height: 2.6em; }
187 tr.message td.subject { padding-left: 20px; }
187 tr.message td.subject { padding-left: 20px; }
188 tr.message td.created_on { white-space: nowrap; }
188 tr.message td.created_on { white-space: nowrap; }
189 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
189 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
190 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
190 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
191 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
191 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
192
192
193 tr.version.closed, tr.version.closed a { color: #999; }
193 tr.version.closed, tr.version.closed a { color: #999; }
194 tr.version td.name { padding-left: 20px; }
194 tr.version td.name { padding-left: 20px; }
195 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
195 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
196 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
196 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
197
197
198 tr.user td { width:13%; }
198 tr.user td { width:13%; }
199 tr.user td.email { width:18%; }
199 tr.user td.email { width:18%; }
200 tr.user td { white-space: nowrap; }
200 tr.user td { white-space: nowrap; }
201 tr.user.locked, tr.user.registered { color: #aaa; }
201 tr.user.locked, tr.user.registered { color: #aaa; }
202 tr.user.locked a, tr.user.registered a { color: #aaa; }
202 tr.user.locked a, tr.user.registered a { color: #aaa; }
203
203
204 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
204 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
205
205
206 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
206 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
207
207
208 tr.time-entry { text-align: center; white-space: nowrap; }
208 tr.time-entry { text-align: center; white-space: nowrap; }
209 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
209 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
210 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
210 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
211 td.hours .hours-dec { font-size: 0.9em; }
211 td.hours .hours-dec { font-size: 0.9em; }
212
212
213 table.plugins td { vertical-align: middle; }
213 table.plugins td { vertical-align: middle; }
214 table.plugins td.configure { text-align: right; padding-right: 1em; }
214 table.plugins td.configure { text-align: right; padding-right: 1em; }
215 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
215 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
216 table.plugins span.description { display: block; font-size: 0.9em; }
216 table.plugins span.description { display: block; font-size: 0.9em; }
217 table.plugins span.url { display: block; font-size: 0.9em; }
217 table.plugins span.url { display: block; font-size: 0.9em; }
218
218
219 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
219 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
220 table.list tbody tr.group span.count { color: #aaa; font-size: 80%; }
220 table.list tbody tr.group span.count { color: #aaa; font-size: 80%; }
221 tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;}
221 tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;}
222 tr.group:hover a.toggle-all { display:inline;}
222 tr.group:hover a.toggle-all { display:inline;}
223 a.toggle-all:hover {text-decoration:none;}
223 a.toggle-all:hover {text-decoration:none;}
224
224
225 table.list tbody tr:hover { background-color:#ffffdd; }
225 table.list tbody tr:hover { background-color:#ffffdd; }
226 table.list tbody tr.group:hover { background-color:inherit; }
226 table.list tbody tr.group:hover { background-color:inherit; }
227 table td {padding:2px;}
227 table td {padding:2px;}
228 table p {margin:0;}
228 table p {margin:0;}
229 .odd {background-color:#f6f7f8;}
229 .odd {background-color:#f6f7f8;}
230 .even {background-color: #fff;}
230 .even {background-color: #fff;}
231
231
232 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
232 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
233 a.sort.asc { background-image: url(../images/sort_asc.png); }
233 a.sort.asc { background-image: url(../images/sort_asc.png); }
234 a.sort.desc { background-image: url(../images/sort_desc.png); }
234 a.sort.desc { background-image: url(../images/sort_desc.png); }
235
235
236 table.attributes { width: 100% }
236 table.attributes { width: 100% }
237 table.attributes th { vertical-align: top; text-align: left; }
237 table.attributes th { vertical-align: top; text-align: left; }
238 table.attributes td { vertical-align: top; }
238 table.attributes td { vertical-align: top; }
239
239
240 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
240 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
241 table.boards td.topic-count, table.boards td.message-count {text-align:center;}
241 table.boards td.topic-count, table.boards td.message-count {text-align:center;}
242 table.boards td.last-message {font-size:80%;}
242 table.boards td.last-message {font-size:80%;}
243
243
244 table.messages td.author, table.messages td.created_on, table.messages td.reply-count {text-align:center;}
244 table.messages td.author, table.messages td.created_on, table.messages td.reply-count {text-align:center;}
245
245
246 table.query-columns {
246 table.query-columns {
247 border-collapse: collapse;
247 border-collapse: collapse;
248 border: 0;
248 border: 0;
249 }
249 }
250
250
251 table.query-columns td.buttons {
251 table.query-columns td.buttons {
252 vertical-align: middle;
252 vertical-align: middle;
253 text-align: center;
253 text-align: center;
254 }
254 }
255
255
256 td.center {text-align:center;}
256 td.center {text-align:center;}
257
257
258 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
258 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
259
259
260 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
260 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
261 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
261 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
262 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
262 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
263 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
263 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
264
264
265 #watchers ul {margin: 0; padding: 0;}
265 #watchers ul {margin: 0; padding: 0;}
266 #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
266 #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
267 #watchers select {width: 95%; display: block;}
267 #watchers select {width: 95%; display: block;}
268 #watchers a.delete {opacity: 0.4;}
268 #watchers a.delete {opacity: 0.4;}
269 #watchers a.delete:hover {opacity: 1;}
269 #watchers a.delete:hover {opacity: 1;}
270 #watchers img.gravatar {margin: 0 4px 2px 0;}
270 #watchers img.gravatar {margin: 0 4px 2px 0;}
271
271
272 span#watchers_inputs {overflow:auto; display:block;}
272 span#watchers_inputs {overflow:auto; display:block;}
273 span.search_for_watchers {display:block;}
273 span.search_for_watchers {display:block;}
274 span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
274 span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
275 span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
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 .highlight { background-color: #FCFD8D;}
278 .highlight { background-color: #FCFD8D;}
279 .highlight.token-1 { background-color: #faa;}
279 .highlight.token-1 { background-color: #faa;}
280 .highlight.token-2 { background-color: #afa;}
280 .highlight.token-2 { background-color: #afa;}
281 .highlight.token-3 { background-color: #aaf;}
281 .highlight.token-3 { background-color: #aaf;}
282
282
283 .box{
283 .box{
284 padding:6px;
284 padding:6px;
285 margin-bottom: 10px;
285 margin-bottom: 10px;
286 background-color:#f6f6f6;
286 background-color:#f6f6f6;
287 color:#505050;
287 color:#505050;
288 line-height:1.5em;
288 line-height:1.5em;
289 border: 1px solid #e4e4e4;
289 border: 1px solid #e4e4e4;
290 }
290 }
291
291
292 div.square {
292 div.square {
293 border: 1px solid #999;
293 border: 1px solid #999;
294 float: left;
294 float: left;
295 margin: .3em .4em 0 .4em;
295 margin: .3em .4em 0 .4em;
296 overflow: hidden;
296 overflow: hidden;
297 width: .6em; height: .6em;
297 width: .6em; height: .6em;
298 }
298 }
299 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
299 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
300 .contextual input, .contextual select {font-size:0.9em;}
300 .contextual input, .contextual select {font-size:0.9em;}
301 .message .contextual { margin-top: 0; }
301 .message .contextual { margin-top: 0; }
302
302
303 .splitcontent {overflow:auto;}
303 .splitcontent {overflow:auto;}
304 .splitcontentleft{float:left; width:49%;}
304 .splitcontentleft{float:left; width:49%;}
305 .splitcontentright{float:right; width:49%;}
305 .splitcontentright{float:right; width:49%;}
306 form {display: inline;}
306 form {display: inline;}
307 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
307 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
308 fieldset {border: 1px solid #e4e4e4; margin:0;}
308 fieldset {border: 1px solid #e4e4e4; margin:0;}
309 legend {color: #484848;}
309 legend {color: #484848;}
310 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
310 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
311 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
311 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
312 blockquote blockquote { margin-left: 0;}
312 blockquote blockquote { margin-left: 0;}
313 acronym { border-bottom: 1px dotted; cursor: help; }
313 acronym { border-bottom: 1px dotted; cursor: help; }
314 textarea.wiki-edit { width: 99%; }
314 textarea.wiki-edit { width: 99%; }
315 li p {margin-top: 0;}
315 li p {margin-top: 0;}
316 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
316 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
317 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
317 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
318 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
318 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
319 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
319 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
320
320
321 div.issue div.subject div div { padding-left: 16px; }
321 div.issue div.subject div div { padding-left: 16px; }
322 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
322 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
323 div.issue div.subject>div>p { margin-top: 0.5em; }
323 div.issue div.subject>div>p { margin-top: 0.5em; }
324 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
324 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
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;}
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 div.issue .next-prev-links {color:#999;}
326 div.issue .next-prev-links {color:#999;}
327 div.issue table.attributes th {width:22%;}
327 div.issue table.attributes th {width:22%;}
328 div.issue table.attributes td {width:28%;}
328 div.issue table.attributes td {width:28%;}
329
329
330 #issue_tree table.issues, #relations table.issues { border: 0; }
330 #issue_tree table.issues, #relations table.issues { border: 0; }
331 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
331 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
332 #relations td.buttons {padding:0;}
332 #relations td.buttons {padding:0;}
333
333
334 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
334 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
335 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
335 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
336 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
336 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
337
337
338 fieldset#date-range p { margin: 2px 0 2px 0; }
338 fieldset#date-range p { margin: 2px 0 2px 0; }
339 fieldset#filters table { border-collapse: collapse; }
339 fieldset#filters table { border-collapse: collapse; }
340 fieldset#filters table td { padding: 0; vertical-align: middle; }
340 fieldset#filters table td { padding: 0; vertical-align: middle; }
341 fieldset#filters tr.filter { height: 2.1em; }
341 fieldset#filters tr.filter { height: 2.1em; }
342 fieldset#filters td.field { width:200px; }
342 fieldset#filters td.field { width:250px; }
343 fieldset#filters td.operator { width:170px; }
343 fieldset#filters td.operator { width:170px; }
344 fieldset#filters td.values { white-space:nowrap; }
344 fieldset#filters td.values { white-space:nowrap; }
345 fieldset#filters td.values select {min-width:130px;}
345 fieldset#filters td.values select {min-width:130px;}
346 fieldset#filters td.values input {height:1em;}
346 fieldset#filters td.values input {height:1em;}
347 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
347 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
348 .toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;}
348 .toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;}
349 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
349 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
350
350
351 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
351 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
352 div#issue-changesets div.changeset { padding: 4px;}
352 div#issue-changesets div.changeset { padding: 4px;}
353 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
353 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
354 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
354 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
355
355
356 .journal ul.details img {margin:0 0 -3px 4px;}
356 .journal ul.details img {margin:0 0 -3px 4px;}
357
357
358 div#activity dl, #search-results { margin-left: 2em; }
358 div#activity dl, #search-results { margin-left: 2em; }
359 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
359 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
360 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
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 div#activity dt.me .time { border-bottom: 1px solid #999; }
361 div#activity dt.me .time { border-bottom: 1px solid #999; }
362 div#activity dt .time { color: #777; font-size: 80%; }
362 div#activity dt .time { color: #777; font-size: 80%; }
363 div#activity dd .description, #search-results dd .description { font-style: italic; }
363 div#activity dd .description, #search-results dd .description { font-style: italic; }
364 div#activity span.project:after, #search-results span.project:after { content: " -"; }
364 div#activity span.project:after, #search-results span.project:after { content: " -"; }
365 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
365 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
366
366
367 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
367 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
368
368
369 div#search-results-counts {float:right;}
369 div#search-results-counts {float:right;}
370 div#search-results-counts ul { margin-top: 0.5em; }
370 div#search-results-counts ul { margin-top: 0.5em; }
371 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
371 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
372
372
373 dt.issue { background-image: url(../images/ticket.png); }
373 dt.issue { background-image: url(../images/ticket.png); }
374 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
374 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
375 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
375 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
376 dt.issue-note { background-image: url(../images/ticket_note.png); }
376 dt.issue-note { background-image: url(../images/ticket_note.png); }
377 dt.changeset { background-image: url(../images/changeset.png); }
377 dt.changeset { background-image: url(../images/changeset.png); }
378 dt.news { background-image: url(../images/news.png); }
378 dt.news { background-image: url(../images/news.png); }
379 dt.message { background-image: url(../images/message.png); }
379 dt.message { background-image: url(../images/message.png); }
380 dt.reply { background-image: url(../images/comments.png); }
380 dt.reply { background-image: url(../images/comments.png); }
381 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
381 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
382 dt.attachment { background-image: url(../images/attachment.png); }
382 dt.attachment { background-image: url(../images/attachment.png); }
383 dt.document { background-image: url(../images/document.png); }
383 dt.document { background-image: url(../images/document.png); }
384 dt.project { background-image: url(../images/projects.png); }
384 dt.project { background-image: url(../images/projects.png); }
385 dt.time-entry { background-image: url(../images/time.png); }
385 dt.time-entry { background-image: url(../images/time.png); }
386
386
387 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
387 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
388
388
389 div#roadmap .related-issues { margin-bottom: 1em; }
389 div#roadmap .related-issues { margin-bottom: 1em; }
390 div#roadmap .related-issues td.checkbox { display: none; }
390 div#roadmap .related-issues td.checkbox { display: none; }
391 div#roadmap .wiki h1:first-child { display: none; }
391 div#roadmap .wiki h1:first-child { display: none; }
392 div#roadmap .wiki h1 { font-size: 120%; }
392 div#roadmap .wiki h1 { font-size: 120%; }
393 div#roadmap .wiki h2 { font-size: 110%; }
393 div#roadmap .wiki h2 { font-size: 110%; }
394 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
394 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
395
395
396 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
396 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
397 div#version-summary fieldset { margin-bottom: 1em; }
397 div#version-summary fieldset { margin-bottom: 1em; }
398 div#version-summary fieldset.time-tracking table { width:100%; }
398 div#version-summary fieldset.time-tracking table { width:100%; }
399 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
399 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
400
400
401 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
401 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
402 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
402 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
403 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
403 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
404 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
404 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
405 table#time-report .hours-dec { font-size: 0.9em; }
405 table#time-report .hours-dec { font-size: 0.9em; }
406
406
407 div.wiki-page .contextual a {opacity: 0.4}
407 div.wiki-page .contextual a {opacity: 0.4}
408 div.wiki-page .contextual a:hover {opacity: 1}
408 div.wiki-page .contextual a:hover {opacity: 1}
409
409
410 form .attributes select { width: 60%; }
410 form .attributes select { width: 60%; }
411 input#issue_subject { width: 99%; }
411 input#issue_subject { width: 99%; }
412 select#issue_done_ratio { width: 95px; }
412 select#issue_done_ratio { width: 95px; }
413
413
414 ul.projects { margin: 0; padding-left: 1em; }
414 ul.projects { margin: 0; padding-left: 1em; }
415 ul.projects.root { margin: 0; padding: 0; }
415 ul.projects.root { margin: 0; padding: 0; }
416 ul.projects ul.projects { border-left: 3px solid #e0e0e0; }
416 ul.projects ul.projects { border-left: 3px solid #e0e0e0; }
417 ul.projects li.root { list-style-type:none; margin-bottom: 1em; }
417 ul.projects li.root { list-style-type:none; margin-bottom: 1em; }
418 ul.projects li.child { list-style-type:none; margin-top: 1em;}
418 ul.projects li.child { list-style-type:none; margin-top: 1em;}
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; }
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 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
420 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
421
421
422 #tracker_project_ids ul { margin: 0; padding-left: 1em; }
422 #tracker_project_ids ul { margin: 0; padding-left: 1em; }
423 #tracker_project_ids li { list-style-type:none; }
423 #tracker_project_ids li { list-style-type:none; }
424
424
425 #related-issues li img {vertical-align:middle;}
425 #related-issues li img {vertical-align:middle;}
426
426
427 ul.properties {padding:0; font-size: 0.9em; color: #777;}
427 ul.properties {padding:0; font-size: 0.9em; color: #777;}
428 ul.properties li {list-style-type:none;}
428 ul.properties li {list-style-type:none;}
429 ul.properties li span {font-style:italic;}
429 ul.properties li span {font-style:italic;}
430
430
431 .total-hours { font-size: 110%; font-weight: bold; }
431 .total-hours { font-size: 110%; font-weight: bold; }
432 .total-hours span.hours-int { font-size: 120%; }
432 .total-hours span.hours-int { font-size: 120%; }
433
433
434 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
434 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
435 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
435 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
436
436
437 #workflow_copy_form select { width: 200px; }
437 #workflow_copy_form select { width: 200px; }
438 table.transitions td.enabled {background: #bfb;}
438 table.transitions td.enabled {background: #bfb;}
439 table.fields_permissions select {font-size:90%}
439 table.fields_permissions select {font-size:90%}
440 table.fields_permissions td.readonly {background:#ddd;}
440 table.fields_permissions td.readonly {background:#ddd;}
441 table.fields_permissions td.required {background:#d88;}
441 table.fields_permissions td.required {background:#d88;}
442
442
443 textarea#custom_field_possible_values {width: 99%}
443 textarea#custom_field_possible_values {width: 99%}
444 input#content_comments {width: 99%}
444 input#content_comments {width: 99%}
445
445
446 .pagination {font-size: 90%}
446 .pagination {font-size: 90%}
447 p.pagination {margin-top:8px;}
447 p.pagination {margin-top:8px;}
448
448
449 /***** Tabular forms ******/
449 /***** Tabular forms ******/
450 .tabular p{
450 .tabular p{
451 margin: 0;
451 margin: 0;
452 padding: 3px 0 3px 0;
452 padding: 3px 0 3px 0;
453 padding-left: 180px; /* width of left column containing the label elements */
453 padding-left: 180px; /* width of left column containing the label elements */
454 min-height: 1.8em;
454 min-height: 1.8em;
455 clear:left;
455 clear:left;
456 }
456 }
457
457
458 html>body .tabular p {overflow:hidden;}
458 html>body .tabular p {overflow:hidden;}
459
459
460 .tabular label{
460 .tabular label{
461 font-weight: bold;
461 font-weight: bold;
462 float: left;
462 float: left;
463 text-align: right;
463 text-align: right;
464 /* width of left column */
464 /* width of left column */
465 margin-left: -180px;
465 margin-left: -180px;
466 /* width of labels. Should be smaller than left column to create some right margin */
466 /* width of labels. Should be smaller than left column to create some right margin */
467 width: 175px;
467 width: 175px;
468 }
468 }
469
469
470 .tabular label.floating{
470 .tabular label.floating{
471 font-weight: normal;
471 font-weight: normal;
472 margin-left: 0px;
472 margin-left: 0px;
473 text-align: left;
473 text-align: left;
474 width: 270px;
474 width: 270px;
475 }
475 }
476
476
477 .tabular label.block{
477 .tabular label.block{
478 font-weight: normal;
478 font-weight: normal;
479 margin-left: 0px !important;
479 margin-left: 0px !important;
480 text-align: left;
480 text-align: left;
481 float: none;
481 float: none;
482 display: block;
482 display: block;
483 width: auto;
483 width: auto;
484 }
484 }
485
485
486 .tabular label.inline{
486 .tabular label.inline{
487 float:none;
487 float:none;
488 margin-left: 5px !important;
488 margin-left: 5px !important;
489 width: auto;
489 width: auto;
490 }
490 }
491
491
492 label.no-css {
492 label.no-css {
493 font-weight: inherit;
493 font-weight: inherit;
494 float:none;
494 float:none;
495 text-align:left;
495 text-align:left;
496 margin-left:0px;
496 margin-left:0px;
497 width:auto;
497 width:auto;
498 }
498 }
499 input#time_entry_comments { width: 90%;}
499 input#time_entry_comments { width: 90%;}
500
500
501 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
501 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
502
502
503 .tabular.settings p{ padding-left: 300px; }
503 .tabular.settings p{ padding-left: 300px; }
504 .tabular.settings label{ margin-left: -300px; width: 295px; }
504 .tabular.settings label{ margin-left: -300px; width: 295px; }
505 .tabular.settings textarea { width: 99%; }
505 .tabular.settings textarea { width: 99%; }
506
506
507 .settings.enabled_scm table {width:100%}
507 .settings.enabled_scm table {width:100%}
508 .settings.enabled_scm td.scm_name{ font-weight: bold; }
508 .settings.enabled_scm td.scm_name{ font-weight: bold; }
509
509
510 fieldset.settings label { display: block; }
510 fieldset.settings label { display: block; }
511 fieldset#notified_events .parent { padding-left: 20px; }
511 fieldset#notified_events .parent { padding-left: 20px; }
512
512
513 span.required {color: #bb0000;}
513 span.required {color: #bb0000;}
514 .summary {font-style: italic;}
514 .summary {font-style: italic;}
515
515
516 #attachments_fields input.description {margin-left: 8px; width:340px;}
516 #attachments_fields input.description {margin-left: 8px; width:340px;}
517 #attachments_fields span {display:block; white-space:nowrap;}
517 #attachments_fields span {display:block; white-space:nowrap;}
518 #attachments_fields img {vertical-align: middle;}
518 #attachments_fields img {vertical-align: middle;}
519
519
520 div.attachments { margin-top: 12px; }
520 div.attachments { margin-top: 12px; }
521 div.attachments p { margin:4px 0 2px 0; }
521 div.attachments p { margin:4px 0 2px 0; }
522 div.attachments img { vertical-align: middle; }
522 div.attachments img { vertical-align: middle; }
523 div.attachments span.author { font-size: 0.9em; color: #888; }
523 div.attachments span.author { font-size: 0.9em; color: #888; }
524
524
525 div.thumbnails {margin-top:0.6em;}
525 div.thumbnails {margin-top:0.6em;}
526 div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;}
526 div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;}
527 div.thumbnails img {margin: 3px;}
527 div.thumbnails img {margin: 3px;}
528
528
529 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
529 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
530 .other-formats span + span:before { content: "| "; }
530 .other-formats span + span:before { content: "| "; }
531
531
532 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
532 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
533
533
534 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
534 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
535 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
535 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
536
536
537 textarea.text_cf {width:90%;}
537 textarea.text_cf {width:90%;}
538
538
539 /* Project members tab */
539 /* Project members tab */
540 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
540 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
541 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
541 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
542 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
542 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
543 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
543 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
544 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
544 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
545 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
545 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
546
546
547 #users_for_watcher {height: 200px; overflow:auto;}
547 #users_for_watcher {height: 200px; overflow:auto;}
548 #users_for_watcher label {display: block;}
548 #users_for_watcher label {display: block;}
549
549
550 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
550 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
551
551
552 input#principal_search, input#user_search {width:100%}
552 input#principal_search, input#user_search {width:100%}
553 input#principal_search, input#user_search {
553 input#principal_search, input#user_search {
554 background: url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px;
554 background: url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px;
555 border:1px solid #9EB1C2; border-radius:3px; height:1.5em; width:95%;
555 border:1px solid #9EB1C2; border-radius:3px; height:1.5em; width:95%;
556 }
556 }
557 input#principal_search.ajax-loading, input#user_search.ajax-loading {
557 input#principal_search.ajax-loading, input#user_search.ajax-loading {
558 background-image: url(../images/loading.gif);
558 background-image: url(../images/loading.gif);
559 }
559 }
560
560
561 * html div#tab-content-members fieldset div { height: 450px; }
561 * html div#tab-content-members fieldset div { height: 450px; }
562
562
563 /***** Flash & error messages ****/
563 /***** Flash & error messages ****/
564 #errorExplanation, div.flash, .nodata, .warning, .conflict {
564 #errorExplanation, div.flash, .nodata, .warning, .conflict {
565 padding: 4px 4px 4px 30px;
565 padding: 4px 4px 4px 30px;
566 margin-bottom: 12px;
566 margin-bottom: 12px;
567 font-size: 1.1em;
567 font-size: 1.1em;
568 border: 2px solid;
568 border: 2px solid;
569 }
569 }
570
570
571 div.flash {margin-top: 8px;}
571 div.flash {margin-top: 8px;}
572
572
573 div.flash.error, #errorExplanation {
573 div.flash.error, #errorExplanation {
574 background: url(../images/exclamation.png) 8px 50% no-repeat;
574 background: url(../images/exclamation.png) 8px 50% no-repeat;
575 background-color: #ffe3e3;
575 background-color: #ffe3e3;
576 border-color: #dd0000;
576 border-color: #dd0000;
577 color: #880000;
577 color: #880000;
578 }
578 }
579
579
580 div.flash.notice {
580 div.flash.notice {
581 background: url(../images/true.png) 8px 5px no-repeat;
581 background: url(../images/true.png) 8px 5px no-repeat;
582 background-color: #dfffdf;
582 background-color: #dfffdf;
583 border-color: #9fcf9f;
583 border-color: #9fcf9f;
584 color: #005f00;
584 color: #005f00;
585 }
585 }
586
586
587 div.flash.warning, .conflict {
587 div.flash.warning, .conflict {
588 background: url(../images/warning.png) 8px 5px no-repeat;
588 background: url(../images/warning.png) 8px 5px no-repeat;
589 background-color: #FFEBC1;
589 background-color: #FFEBC1;
590 border-color: #FDBF3B;
590 border-color: #FDBF3B;
591 color: #A6750C;
591 color: #A6750C;
592 text-align: left;
592 text-align: left;
593 }
593 }
594
594
595 .nodata, .warning {
595 .nodata, .warning {
596 text-align: center;
596 text-align: center;
597 background-color: #FFEBC1;
597 background-color: #FFEBC1;
598 border-color: #FDBF3B;
598 border-color: #FDBF3B;
599 color: #A6750C;
599 color: #A6750C;
600 }
600 }
601
601
602 #errorExplanation ul { font-size: 0.9em;}
602 #errorExplanation ul { font-size: 0.9em;}
603 #errorExplanation h2, #errorExplanation p { display: none; }
603 #errorExplanation h2, #errorExplanation p { display: none; }
604
604
605 .conflict-details {font-size:80%;}
605 .conflict-details {font-size:80%;}
606
606
607 /***** Ajax indicator ******/
607 /***** Ajax indicator ******/
608 #ajax-indicator {
608 #ajax-indicator {
609 position: absolute; /* fixed not supported by IE */
609 position: absolute; /* fixed not supported by IE */
610 background-color:#eee;
610 background-color:#eee;
611 border: 1px solid #bbb;
611 border: 1px solid #bbb;
612 top:35%;
612 top:35%;
613 left:40%;
613 left:40%;
614 width:20%;
614 width:20%;
615 font-weight:bold;
615 font-weight:bold;
616 text-align:center;
616 text-align:center;
617 padding:0.6em;
617 padding:0.6em;
618 z-index:100;
618 z-index:100;
619 opacity: 0.5;
619 opacity: 0.5;
620 }
620 }
621
621
622 html>body #ajax-indicator { position: fixed; }
622 html>body #ajax-indicator { position: fixed; }
623
623
624 #ajax-indicator span {
624 #ajax-indicator span {
625 background-position: 0% 40%;
625 background-position: 0% 40%;
626 background-repeat: no-repeat;
626 background-repeat: no-repeat;
627 background-image: url(../images/loading.gif);
627 background-image: url(../images/loading.gif);
628 padding-left: 26px;
628 padding-left: 26px;
629 vertical-align: bottom;
629 vertical-align: bottom;
630 }
630 }
631
631
632 /***** Calendar *****/
632 /***** Calendar *****/
633 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
633 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
634 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
634 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
635 table.cal thead th.week-number {width: auto;}
635 table.cal thead th.week-number {width: auto;}
636 table.cal tbody tr {height: 100px;}
636 table.cal tbody tr {height: 100px;}
637 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
637 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
638 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
638 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
639 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
639 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
640 table.cal td.odd p.day-num {color: #bbb;}
640 table.cal td.odd p.day-num {color: #bbb;}
641 table.cal td.today {background:#ffffdd;}
641 table.cal td.today {background:#ffffdd;}
642 table.cal td.today p.day-num {font-weight: bold;}
642 table.cal td.today p.day-num {font-weight: bold;}
643 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
643 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
644 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
644 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
645 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
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 p.cal.legend span {display:block;}
646 p.cal.legend span {display:block;}
647
647
648 /***** Tooltips ******/
648 /***** Tooltips ******/
649 .tooltip{position:relative;z-index:24;}
649 .tooltip{position:relative;z-index:24;}
650 .tooltip:hover{z-index:25;color:#000;}
650 .tooltip:hover{z-index:25;color:#000;}
651 .tooltip span.tip{display: none; text-align:left;}
651 .tooltip span.tip{display: none; text-align:left;}
652
652
653 div.tooltip:hover span.tip{
653 div.tooltip:hover span.tip{
654 display:block;
654 display:block;
655 position:absolute;
655 position:absolute;
656 top:12px; left:24px; width:270px;
656 top:12px; left:24px; width:270px;
657 border:1px solid #555;
657 border:1px solid #555;
658 background-color:#fff;
658 background-color:#fff;
659 padding: 4px;
659 padding: 4px;
660 font-size: 0.8em;
660 font-size: 0.8em;
661 color:#505050;
661 color:#505050;
662 }
662 }
663
663
664 img.ui-datepicker-trigger {
664 img.ui-datepicker-trigger {
665 cursor: pointer;
665 cursor: pointer;
666 vertical-align: middle;
666 vertical-align: middle;
667 margin-left: 4px;
667 margin-left: 4px;
668 }
668 }
669
669
670 /***** Progress bar *****/
670 /***** Progress bar *****/
671 table.progress {
671 table.progress {
672 border-collapse: collapse;
672 border-collapse: collapse;
673 border-spacing: 0pt;
673 border-spacing: 0pt;
674 empty-cells: show;
674 empty-cells: show;
675 text-align: center;
675 text-align: center;
676 float:left;
676 float:left;
677 margin: 1px 6px 1px 0px;
677 margin: 1px 6px 1px 0px;
678 }
678 }
679
679
680 table.progress td { height: 1em; }
680 table.progress td { height: 1em; }
681 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
681 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
682 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
682 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
683 table.progress td.todo { background: #eee none repeat scroll 0%; }
683 table.progress td.todo { background: #eee none repeat scroll 0%; }
684 p.pourcent {font-size: 80%;}
684 p.pourcent {font-size: 80%;}
685 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
685 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
686
686
687 #roadmap table.progress td { height: 1.2em; }
687 #roadmap table.progress td { height: 1.2em; }
688 /***** Tabs *****/
688 /***** Tabs *****/
689 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
689 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
690 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
690 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
691 #content .tabs ul li {
691 #content .tabs ul li {
692 float:left;
692 float:left;
693 list-style-type:none;
693 list-style-type:none;
694 white-space:nowrap;
694 white-space:nowrap;
695 margin-right:4px;
695 margin-right:4px;
696 background:#fff;
696 background:#fff;
697 position:relative;
697 position:relative;
698 margin-bottom:-1px;
698 margin-bottom:-1px;
699 }
699 }
700 #content .tabs ul li a{
700 #content .tabs ul li a{
701 display:block;
701 display:block;
702 font-size: 0.9em;
702 font-size: 0.9em;
703 text-decoration:none;
703 text-decoration:none;
704 line-height:1.3em;
704 line-height:1.3em;
705 padding:4px 6px 4px 6px;
705 padding:4px 6px 4px 6px;
706 border: 1px solid #ccc;
706 border: 1px solid #ccc;
707 border-bottom: 1px solid #bbbbbb;
707 border-bottom: 1px solid #bbbbbb;
708 background-color: #f6f6f6;
708 background-color: #f6f6f6;
709 color:#999;
709 color:#999;
710 font-weight:bold;
710 font-weight:bold;
711 border-top-left-radius:3px;
711 border-top-left-radius:3px;
712 border-top-right-radius:3px;
712 border-top-right-radius:3px;
713 }
713 }
714
714
715 #content .tabs ul li a:hover {
715 #content .tabs ul li a:hover {
716 background-color: #ffffdd;
716 background-color: #ffffdd;
717 text-decoration:none;
717 text-decoration:none;
718 }
718 }
719
719
720 #content .tabs ul li a.selected {
720 #content .tabs ul li a.selected {
721 background-color: #fff;
721 background-color: #fff;
722 border: 1px solid #bbbbbb;
722 border: 1px solid #bbbbbb;
723 border-bottom: 1px solid #fff;
723 border-bottom: 1px solid #fff;
724 color:#444;
724 color:#444;
725 }
725 }
726
726
727 #content .tabs ul li a.selected:hover {background-color: #fff;}
727 #content .tabs ul li a.selected:hover {background-color: #fff;}
728
728
729 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
729 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
730
730
731 button.tab-left, button.tab-right {
731 button.tab-left, button.tab-right {
732 font-size: 0.9em;
732 font-size: 0.9em;
733 cursor: pointer;
733 cursor: pointer;
734 height:24px;
734 height:24px;
735 border: 1px solid #ccc;
735 border: 1px solid #ccc;
736 border-bottom: 1px solid #bbbbbb;
736 border-bottom: 1px solid #bbbbbb;
737 position:absolute;
737 position:absolute;
738 padding:4px;
738 padding:4px;
739 width: 20px;
739 width: 20px;
740 bottom: -1px;
740 bottom: -1px;
741 }
741 }
742
742
743 button.tab-left {
743 button.tab-left {
744 right: 20px;
744 right: 20px;
745 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
745 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
746 border-top-left-radius:3px;
746 border-top-left-radius:3px;
747 }
747 }
748
748
749 button.tab-right {
749 button.tab-right {
750 right: 0;
750 right: 0;
751 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
751 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
752 border-top-right-radius:3px;
752 border-top-right-radius:3px;
753 }
753 }
754
754
755 /***** Diff *****/
755 /***** Diff *****/
756 .diff_out { background: #fcc; }
756 .diff_out { background: #fcc; }
757 .diff_out span { background: #faa; }
757 .diff_out span { background: #faa; }
758 .diff_in { background: #cfc; }
758 .diff_in { background: #cfc; }
759 .diff_in span { background: #afa; }
759 .diff_in span { background: #afa; }
760
760
761 .text-diff {
761 .text-diff {
762 padding: 1em;
762 padding: 1em;
763 background-color:#f6f6f6;
763 background-color:#f6f6f6;
764 color:#505050;
764 color:#505050;
765 border: 1px solid #e4e4e4;
765 border: 1px solid #e4e4e4;
766 }
766 }
767
767
768 /***** Wiki *****/
768 /***** Wiki *****/
769 div.wiki table {
769 div.wiki table {
770 border-collapse: collapse;
770 border-collapse: collapse;
771 margin-bottom: 1em;
771 margin-bottom: 1em;
772 }
772 }
773
773
774 div.wiki table, div.wiki td, div.wiki th {
774 div.wiki table, div.wiki td, div.wiki th {
775 border: 1px solid #bbb;
775 border: 1px solid #bbb;
776 padding: 4px;
776 padding: 4px;
777 }
777 }
778
778
779 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
779 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
780
780
781 div.wiki .external {
781 div.wiki .external {
782 background-position: 0% 60%;
782 background-position: 0% 60%;
783 background-repeat: no-repeat;
783 background-repeat: no-repeat;
784 padding-left: 12px;
784 padding-left: 12px;
785 background-image: url(../images/external.png);
785 background-image: url(../images/external.png);
786 }
786 }
787
787
788 div.wiki a.new {color: #b73535;}
788 div.wiki a.new {color: #b73535;}
789
789
790 div.wiki ul, div.wiki ol {margin-bottom:1em;}
790 div.wiki ul, div.wiki ol {margin-bottom:1em;}
791
791
792 div.wiki pre {
792 div.wiki pre {
793 margin: 1em 1em 1em 1.6em;
793 margin: 1em 1em 1em 1.6em;
794 padding: 8px;
794 padding: 8px;
795 background-color: #fafafa;
795 background-color: #fafafa;
796 border: 1px solid #e2e2e2;
796 border: 1px solid #e2e2e2;
797 width:auto;
797 width:auto;
798 overflow-x: auto;
798 overflow-x: auto;
799 overflow-y: hidden;
799 overflow-y: hidden;
800 }
800 }
801
801
802 div.wiki ul.toc {
802 div.wiki ul.toc {
803 background-color: #ffffdd;
803 background-color: #ffffdd;
804 border: 1px solid #e4e4e4;
804 border: 1px solid #e4e4e4;
805 padding: 4px;
805 padding: 4px;
806 line-height: 1.2em;
806 line-height: 1.2em;
807 margin-bottom: 12px;
807 margin-bottom: 12px;
808 margin-right: 12px;
808 margin-right: 12px;
809 margin-left: 0;
809 margin-left: 0;
810 display: table
810 display: table
811 }
811 }
812 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
812 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
813
813
814 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
814 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
815 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
815 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
816 div.wiki ul.toc ul { margin: 0; padding: 0; }
816 div.wiki ul.toc ul { margin: 0; padding: 0; }
817 div.wiki ul.toc li { list-style-type:none; margin: 0;}
817 div.wiki ul.toc li { list-style-type:none; margin: 0;}
818 div.wiki ul.toc li li { margin-left: 1.5em; }
818 div.wiki ul.toc li li { margin-left: 1.5em; }
819 div.wiki ul.toc li li li { font-size: 0.8em; }
819 div.wiki ul.toc li li li { font-size: 0.8em; }
820 div.wiki ul.toc a {
820 div.wiki ul.toc a {
821 font-size: 0.9em;
821 font-size: 0.9em;
822 font-weight: normal;
822 font-weight: normal;
823 text-decoration: none;
823 text-decoration: none;
824 color: #606060;
824 color: #606060;
825 }
825 }
826 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
826 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
827
827
828 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
828 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
829 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
829 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
830 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
830 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
831
831
832 div.wiki img { vertical-align: middle; }
832 div.wiki img { vertical-align: middle; }
833
833
834 /***** My page layout *****/
834 /***** My page layout *****/
835 .block-receiver {
835 .block-receiver {
836 border:1px dashed #c0c0c0;
836 border:1px dashed #c0c0c0;
837 margin-bottom: 20px;
837 margin-bottom: 20px;
838 padding: 15px 0 15px 0;
838 padding: 15px 0 15px 0;
839 }
839 }
840
840
841 .mypage-box {
841 .mypage-box {
842 margin:0 0 20px 0;
842 margin:0 0 20px 0;
843 color:#505050;
843 color:#505050;
844 line-height:1.5em;
844 line-height:1.5em;
845 }
845 }
846
846
847 .handle {cursor: move;}
847 .handle {cursor: move;}
848
848
849 a.close-icon {
849 a.close-icon {
850 display:block;
850 display:block;
851 margin-top:3px;
851 margin-top:3px;
852 overflow:hidden;
852 overflow:hidden;
853 width:12px;
853 width:12px;
854 height:12px;
854 height:12px;
855 background-repeat: no-repeat;
855 background-repeat: no-repeat;
856 cursor:pointer;
856 cursor:pointer;
857 background-image:url('../images/close.png');
857 background-image:url('../images/close.png');
858 }
858 }
859 a.close-icon:hover {background-image:url('../images/close_hl.png');}
859 a.close-icon:hover {background-image:url('../images/close_hl.png');}
860
860
861 /***** Gantt chart *****/
861 /***** Gantt chart *****/
862 .gantt_hdr {
862 .gantt_hdr {
863 position:absolute;
863 position:absolute;
864 top:0;
864 top:0;
865 height:16px;
865 height:16px;
866 border-top: 1px solid #c0c0c0;
866 border-top: 1px solid #c0c0c0;
867 border-bottom: 1px solid #c0c0c0;
867 border-bottom: 1px solid #c0c0c0;
868 border-right: 1px solid #c0c0c0;
868 border-right: 1px solid #c0c0c0;
869 text-align: center;
869 text-align: center;
870 overflow: hidden;
870 overflow: hidden;
871 }
871 }
872
872
873 .gantt_subjects { font-size: 0.8em; }
873 .gantt_subjects { font-size: 0.8em; }
874 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
874 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
875
875
876 .task {
876 .task {
877 position: absolute;
877 position: absolute;
878 height:8px;
878 height:8px;
879 font-size:0.8em;
879 font-size:0.8em;
880 color:#888;
880 color:#888;
881 padding:0;
881 padding:0;
882 margin:0;
882 margin:0;
883 line-height:16px;
883 line-height:16px;
884 white-space:nowrap;
884 white-space:nowrap;
885 }
885 }
886
886
887 .task.label {width:100%;}
887 .task.label {width:100%;}
888 .task.label.project, .task.label.version { font-weight: bold; }
888 .task.label.project, .task.label.version { font-weight: bold; }
889
889
890 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
890 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
891 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
891 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
892 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
892 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
893
893
894 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
894 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
895 .task_late.parent, .task_done.parent { height: 3px;}
895 .task_late.parent, .task_done.parent { height: 3px;}
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;}
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 .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;}
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 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
899 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
900 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
900 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
901 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
901 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
902 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
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 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
904 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
905 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
905 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
906 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
906 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
907 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
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 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
909 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
910 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
910 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
911
911
912 /***** Icons *****/
912 /***** Icons *****/
913 .icon {
913 .icon {
914 background-position: 0% 50%;
914 background-position: 0% 50%;
915 background-repeat: no-repeat;
915 background-repeat: no-repeat;
916 padding-left: 20px;
916 padding-left: 20px;
917 padding-top: 2px;
917 padding-top: 2px;
918 padding-bottom: 3px;
918 padding-bottom: 3px;
919 }
919 }
920
920
921 .icon-add { background-image: url(../images/add.png); }
921 .icon-add { background-image: url(../images/add.png); }
922 .icon-edit { background-image: url(../images/edit.png); }
922 .icon-edit { background-image: url(../images/edit.png); }
923 .icon-copy { background-image: url(../images/copy.png); }
923 .icon-copy { background-image: url(../images/copy.png); }
924 .icon-duplicate { background-image: url(../images/duplicate.png); }
924 .icon-duplicate { background-image: url(../images/duplicate.png); }
925 .icon-del { background-image: url(../images/delete.png); }
925 .icon-del { background-image: url(../images/delete.png); }
926 .icon-move { background-image: url(../images/move.png); }
926 .icon-move { background-image: url(../images/move.png); }
927 .icon-save { background-image: url(../images/save.png); }
927 .icon-save { background-image: url(../images/save.png); }
928 .icon-cancel { background-image: url(../images/cancel.png); }
928 .icon-cancel { background-image: url(../images/cancel.png); }
929 .icon-multiple { background-image: url(../images/table_multiple.png); }
929 .icon-multiple { background-image: url(../images/table_multiple.png); }
930 .icon-folder { background-image: url(../images/folder.png); }
930 .icon-folder { background-image: url(../images/folder.png); }
931 .open .icon-folder { background-image: url(../images/folder_open.png); }
931 .open .icon-folder { background-image: url(../images/folder_open.png); }
932 .icon-package { background-image: url(../images/package.png); }
932 .icon-package { background-image: url(../images/package.png); }
933 .icon-user { background-image: url(../images/user.png); }
933 .icon-user { background-image: url(../images/user.png); }
934 .icon-projects { background-image: url(../images/projects.png); }
934 .icon-projects { background-image: url(../images/projects.png); }
935 .icon-help { background-image: url(../images/help.png); }
935 .icon-help { background-image: url(../images/help.png); }
936 .icon-attachment { background-image: url(../images/attachment.png); }
936 .icon-attachment { background-image: url(../images/attachment.png); }
937 .icon-history { background-image: url(../images/history.png); }
937 .icon-history { background-image: url(../images/history.png); }
938 .icon-time { background-image: url(../images/time.png); }
938 .icon-time { background-image: url(../images/time.png); }
939 .icon-time-add { background-image: url(../images/time_add.png); }
939 .icon-time-add { background-image: url(../images/time_add.png); }
940 .icon-stats { background-image: url(../images/stats.png); }
940 .icon-stats { background-image: url(../images/stats.png); }
941 .icon-warning { background-image: url(../images/warning.png); }
941 .icon-warning { background-image: url(../images/warning.png); }
942 .icon-fav { background-image: url(../images/fav.png); }
942 .icon-fav { background-image: url(../images/fav.png); }
943 .icon-fav-off { background-image: url(../images/fav_off.png); }
943 .icon-fav-off { background-image: url(../images/fav_off.png); }
944 .icon-reload { background-image: url(../images/reload.png); }
944 .icon-reload { background-image: url(../images/reload.png); }
945 .icon-lock { background-image: url(../images/locked.png); }
945 .icon-lock { background-image: url(../images/locked.png); }
946 .icon-unlock { background-image: url(../images/unlock.png); }
946 .icon-unlock { background-image: url(../images/unlock.png); }
947 .icon-checked { background-image: url(../images/true.png); }
947 .icon-checked { background-image: url(../images/true.png); }
948 .icon-details { background-image: url(../images/zoom_in.png); }
948 .icon-details { background-image: url(../images/zoom_in.png); }
949 .icon-report { background-image: url(../images/report.png); }
949 .icon-report { background-image: url(../images/report.png); }
950 .icon-comment { background-image: url(../images/comment.png); }
950 .icon-comment { background-image: url(../images/comment.png); }
951 .icon-summary { background-image: url(../images/lightning.png); }
951 .icon-summary { background-image: url(../images/lightning.png); }
952 .icon-server-authentication { background-image: url(../images/server_key.png); }
952 .icon-server-authentication { background-image: url(../images/server_key.png); }
953 .icon-issue { background-image: url(../images/ticket.png); }
953 .icon-issue { background-image: url(../images/ticket.png); }
954 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
954 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
955 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
955 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
956 .icon-passwd { background-image: url(../images/textfield_key.png); }
956 .icon-passwd { background-image: url(../images/textfield_key.png); }
957 .icon-test { background-image: url(../images/bullet_go.png); }
957 .icon-test { background-image: url(../images/bullet_go.png); }
958
958
959 .icon-file { background-image: url(../images/files/default.png); }
959 .icon-file { background-image: url(../images/files/default.png); }
960 .icon-file.text-plain { background-image: url(../images/files/text.png); }
960 .icon-file.text-plain { background-image: url(../images/files/text.png); }
961 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
961 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
962 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
962 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
963 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
963 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
964 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
964 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
965 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
965 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
966 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
966 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
967 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
967 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
968 .icon-file.text-css { background-image: url(../images/files/css.png); }
968 .icon-file.text-css { background-image: url(../images/files/css.png); }
969 .icon-file.text-html { background-image: url(../images/files/html.png); }
969 .icon-file.text-html { background-image: url(../images/files/html.png); }
970 .icon-file.image-gif { background-image: url(../images/files/image.png); }
970 .icon-file.image-gif { background-image: url(../images/files/image.png); }
971 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
971 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
972 .icon-file.image-png { background-image: url(../images/files/image.png); }
972 .icon-file.image-png { background-image: url(../images/files/image.png); }
973 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
973 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
974 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
974 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
975 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
975 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
976 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
976 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
977
977
978 img.gravatar {
978 img.gravatar {
979 padding: 2px;
979 padding: 2px;
980 border: solid 1px #d5d5d5;
980 border: solid 1px #d5d5d5;
981 background: #fff;
981 background: #fff;
982 vertical-align: middle;
982 vertical-align: middle;
983 }
983 }
984
984
985 div.issue img.gravatar {
985 div.issue img.gravatar {
986 float: left;
986 float: left;
987 margin: 0 6px 0 0;
987 margin: 0 6px 0 0;
988 padding: 5px;
988 padding: 5px;
989 }
989 }
990
990
991 div.issue table img.gravatar {
991 div.issue table img.gravatar {
992 height: 14px;
992 height: 14px;
993 width: 14px;
993 width: 14px;
994 padding: 2px;
994 padding: 2px;
995 float: left;
995 float: left;
996 margin: 0 0.5em 0 0;
996 margin: 0 0.5em 0 0;
997 }
997 }
998
998
999 h2 img.gravatar {margin: -2px 4px -4px 0;}
999 h2 img.gravatar {margin: -2px 4px -4px 0;}
1000 h3 img.gravatar {margin: -4px 4px -4px 0;}
1000 h3 img.gravatar {margin: -4px 4px -4px 0;}
1001 h4 img.gravatar {margin: -6px 4px -4px 0;}
1001 h4 img.gravatar {margin: -6px 4px -4px 0;}
1002 td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
1002 td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
1003 #activity dt img.gravatar {float: left; margin: 0 1em 1em 0;}
1003 #activity dt img.gravatar {float: left; margin: 0 1em 1em 0;}
1004 /* Used on 12px Gravatar img tags without the icon background */
1004 /* Used on 12px Gravatar img tags without the icon background */
1005 .icon-gravatar {float: left; margin-right: 4px;}
1005 .icon-gravatar {float: left; margin-right: 4px;}
1006
1006
1007 #activity dt, .journal {clear: left;}
1007 #activity dt, .journal {clear: left;}
1008
1008
1009 .journal-link {float: right;}
1009 .journal-link {float: right;}
1010
1010
1011 h2 img { vertical-align:middle; }
1011 h2 img { vertical-align:middle; }
1012
1012
1013 .hascontextmenu { cursor: context-menu; }
1013 .hascontextmenu { cursor: context-menu; }
1014
1014
1015 /************* CodeRay styles *************/
1015 /************* CodeRay styles *************/
1016 .syntaxhl div {display: inline;}
1016 .syntaxhl div {display: inline;}
1017 .syntaxhl .line-numbers {padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px;}
1017 .syntaxhl .line-numbers {padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px;}
1018 .syntaxhl .code pre { overflow: auto }
1018 .syntaxhl .code pre { overflow: auto }
1019 .syntaxhl .debug { color: white !important; background: blue !important; }
1019 .syntaxhl .debug { color: white !important; background: blue !important; }
1020
1020
1021 .syntaxhl .annotation { color:#007 }
1021 .syntaxhl .annotation { color:#007 }
1022 .syntaxhl .attribute-name { color:#b48 }
1022 .syntaxhl .attribute-name { color:#b48 }
1023 .syntaxhl .attribute-value { color:#700 }
1023 .syntaxhl .attribute-value { color:#700 }
1024 .syntaxhl .binary { color:#509 }
1024 .syntaxhl .binary { color:#509 }
1025 .syntaxhl .char .content { color:#D20 }
1025 .syntaxhl .char .content { color:#D20 }
1026 .syntaxhl .char .delimiter { color:#710 }
1026 .syntaxhl .char .delimiter { color:#710 }
1027 .syntaxhl .char { color:#D20 }
1027 .syntaxhl .char { color:#D20 }
1028 .syntaxhl .class { color:#258; font-weight:bold }
1028 .syntaxhl .class { color:#258; font-weight:bold }
1029 .syntaxhl .class-variable { color:#369 }
1029 .syntaxhl .class-variable { color:#369 }
1030 .syntaxhl .color { color:#0A0 }
1030 .syntaxhl .color { color:#0A0 }
1031 .syntaxhl .comment { color:#385 }
1031 .syntaxhl .comment { color:#385 }
1032 .syntaxhl .comment .char { color:#385 }
1032 .syntaxhl .comment .char { color:#385 }
1033 .syntaxhl .comment .delimiter { color:#385 }
1033 .syntaxhl .comment .delimiter { color:#385 }
1034 .syntaxhl .complex { color:#A08 }
1034 .syntaxhl .complex { color:#A08 }
1035 .syntaxhl .constant { color:#258; font-weight:bold }
1035 .syntaxhl .constant { color:#258; font-weight:bold }
1036 .syntaxhl .decorator { color:#B0B }
1036 .syntaxhl .decorator { color:#B0B }
1037 .syntaxhl .definition { color:#099; font-weight:bold }
1037 .syntaxhl .definition { color:#099; font-weight:bold }
1038 .syntaxhl .delimiter { color:black }
1038 .syntaxhl .delimiter { color:black }
1039 .syntaxhl .directive { color:#088; font-weight:bold }
1039 .syntaxhl .directive { color:#088; font-weight:bold }
1040 .syntaxhl .doc { color:#970 }
1040 .syntaxhl .doc { color:#970 }
1041 .syntaxhl .doc-string { color:#D42; font-weight:bold }
1041 .syntaxhl .doc-string { color:#D42; font-weight:bold }
1042 .syntaxhl .doctype { color:#34b }
1042 .syntaxhl .doctype { color:#34b }
1043 .syntaxhl .entity { color:#800; font-weight:bold }
1043 .syntaxhl .entity { color:#800; font-weight:bold }
1044 .syntaxhl .error { color:#F00; background-color:#FAA }
1044 .syntaxhl .error { color:#F00; background-color:#FAA }
1045 .syntaxhl .escape { color:#666 }
1045 .syntaxhl .escape { color:#666 }
1046 .syntaxhl .exception { color:#C00; font-weight:bold }
1046 .syntaxhl .exception { color:#C00; font-weight:bold }
1047 .syntaxhl .float { color:#06D }
1047 .syntaxhl .float { color:#06D }
1048 .syntaxhl .function { color:#06B; font-weight:bold }
1048 .syntaxhl .function { color:#06B; font-weight:bold }
1049 .syntaxhl .global-variable { color:#d70 }
1049 .syntaxhl .global-variable { color:#d70 }
1050 .syntaxhl .hex { color:#02b }
1050 .syntaxhl .hex { color:#02b }
1051 .syntaxhl .imaginary { color:#f00 }
1051 .syntaxhl .imaginary { color:#f00 }
1052 .syntaxhl .include { color:#B44; font-weight:bold }
1052 .syntaxhl .include { color:#B44; font-weight:bold }
1053 .syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
1053 .syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
1054 .syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
1054 .syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
1055 .syntaxhl .instance-variable { color:#33B }
1055 .syntaxhl .instance-variable { color:#33B }
1056 .syntaxhl .integer { color:#06D }
1056 .syntaxhl .integer { color:#06D }
1057 .syntaxhl .key .char { color: #60f }
1057 .syntaxhl .key .char { color: #60f }
1058 .syntaxhl .key .delimiter { color: #404 }
1058 .syntaxhl .key .delimiter { color: #404 }
1059 .syntaxhl .key { color: #606 }
1059 .syntaxhl .key { color: #606 }
1060 .syntaxhl .keyword { color:#939; font-weight:bold }
1060 .syntaxhl .keyword { color:#939; font-weight:bold }
1061 .syntaxhl .label { color:#970; font-weight:bold }
1061 .syntaxhl .label { color:#970; font-weight:bold }
1062 .syntaxhl .local-variable { color:#963 }
1062 .syntaxhl .local-variable { color:#963 }
1063 .syntaxhl .namespace { color:#707; font-weight:bold }
1063 .syntaxhl .namespace { color:#707; font-weight:bold }
1064 .syntaxhl .octal { color:#40E }
1064 .syntaxhl .octal { color:#40E }
1065 .syntaxhl .operator { }
1065 .syntaxhl .operator { }
1066 .syntaxhl .predefined { color:#369; font-weight:bold }
1066 .syntaxhl .predefined { color:#369; font-weight:bold }
1067 .syntaxhl .predefined-constant { color:#069 }
1067 .syntaxhl .predefined-constant { color:#069 }
1068 .syntaxhl .predefined-type { color:#0a5; font-weight:bold }
1068 .syntaxhl .predefined-type { color:#0a5; font-weight:bold }
1069 .syntaxhl .preprocessor { color:#579 }
1069 .syntaxhl .preprocessor { color:#579 }
1070 .syntaxhl .pseudo-class { color:#00C; font-weight:bold }
1070 .syntaxhl .pseudo-class { color:#00C; font-weight:bold }
1071 .syntaxhl .regexp .content { color:#808 }
1071 .syntaxhl .regexp .content { color:#808 }
1072 .syntaxhl .regexp .delimiter { color:#404 }
1072 .syntaxhl .regexp .delimiter { color:#404 }
1073 .syntaxhl .regexp .modifier { color:#C2C }
1073 .syntaxhl .regexp .modifier { color:#C2C }
1074 .syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
1074 .syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
1075 .syntaxhl .reserved { color:#080; font-weight:bold }
1075 .syntaxhl .reserved { color:#080; font-weight:bold }
1076 .syntaxhl .shell .content { color:#2B2 }
1076 .syntaxhl .shell .content { color:#2B2 }
1077 .syntaxhl .shell .delimiter { color:#161 }
1077 .syntaxhl .shell .delimiter { color:#161 }
1078 .syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
1078 .syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
1079 .syntaxhl .string .char { color: #46a }
1079 .syntaxhl .string .char { color: #46a }
1080 .syntaxhl .string .content { color: #46a }
1080 .syntaxhl .string .content { color: #46a }
1081 .syntaxhl .string .delimiter { color: #46a }
1081 .syntaxhl .string .delimiter { color: #46a }
1082 .syntaxhl .string .modifier { color: #46a }
1082 .syntaxhl .string .modifier { color: #46a }
1083 .syntaxhl .symbol .content { color:#d33 }
1083 .syntaxhl .symbol .content { color:#d33 }
1084 .syntaxhl .symbol .delimiter { color:#d33 }
1084 .syntaxhl .symbol .delimiter { color:#d33 }
1085 .syntaxhl .symbol { color:#d33 }
1085 .syntaxhl .symbol { color:#d33 }
1086 .syntaxhl .tag { color:#070 }
1086 .syntaxhl .tag { color:#070 }
1087 .syntaxhl .type { color:#339; font-weight:bold }
1087 .syntaxhl .type { color:#339; font-weight:bold }
1088 .syntaxhl .value { color: #088; }
1088 .syntaxhl .value { color: #088; }
1089 .syntaxhl .variable { color:#037 }
1089 .syntaxhl .variable { color:#037 }
1090
1090
1091 .syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
1091 .syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
1092 .syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
1092 .syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
1093 .syntaxhl .change { color: #bbf; background: #007; }
1093 .syntaxhl .change { color: #bbf; background: #007; }
1094 .syntaxhl .head { color: #f8f; background: #505 }
1094 .syntaxhl .head { color: #f8f; background: #505 }
1095 .syntaxhl .head .filename { color: white; }
1095 .syntaxhl .head .filename { color: white; }
1096
1096
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; }
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 .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; }
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 .syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
1100 .syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
1101 .syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
1101 .syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
1102 .syntaxhl .change .change { color: #88f }
1102 .syntaxhl .change .change { color: #88f }
1103 .syntaxhl .head .head { color: #f4f }
1103 .syntaxhl .head .head { color: #f4f }
1104
1104
1105 /***** Media print specific styles *****/
1105 /***** Media print specific styles *****/
1106 @media print {
1106 @media print {
1107 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1107 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1108 #main { background: #fff; }
1108 #main { background: #fff; }
1109 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1109 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1110 #wiki_add_attachment { display:none; }
1110 #wiki_add_attachment { display:none; }
1111 .hide-when-print { display: none; }
1111 .hide-when-print { display: none; }
1112 .autoscroll {overflow-x: visible;}
1112 .autoscroll {overflow-x: visible;}
1113 table.list {margin-top:0.5em;}
1113 table.list {margin-top:0.5em;}
1114 table.list th, table.list td {border: 1px solid #aaa;}
1114 table.list th, table.list td {border: 1px solid #aaa;}
1115 }
1115 }
1116
1116
1117 /* Accessibility specific styles */
1117 /* Accessibility specific styles */
1118 .hidden-for-sighted {
1118 .hidden-for-sighted {
1119 position:absolute;
1119 position:absolute;
1120 left:-10000px;
1120 left:-10000px;
1121 top:auto;
1121 top:auto;
1122 width:1px;
1122 width:1px;
1123 height:1px;
1123 height:1px;
1124 overflow:hidden;
1124 overflow:hidden;
1125 }
1125 }
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,1073 +1,1118
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class QueryTest < ActiveSupport::TestCase
20 class QueryTest < ActiveSupport::TestCase
21 fixtures :projects, :enabled_modules, :users, :members,
21 fixtures :projects, :enabled_modules, :users, :members,
22 :member_roles, :roles, :trackers, :issue_statuses,
22 :member_roles, :roles, :trackers, :issue_statuses,
23 :issue_categories, :enumerations, :issues,
23 :issue_categories, :enumerations, :issues,
24 :watchers, :custom_fields, :custom_values, :versions,
24 :watchers, :custom_fields, :custom_values, :versions,
25 :queries,
25 :queries,
26 :projects_trackers,
26 :projects_trackers,
27 :custom_fields_trackers
27 :custom_fields_trackers
28
28
29 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
29 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
30 query = Query.new(:project => nil, :name => '_')
30 query = Query.new(:project => nil, :name => '_')
31 assert query.available_filters.has_key?('cf_1')
31 assert query.available_filters.has_key?('cf_1')
32 assert !query.available_filters.has_key?('cf_3')
32 assert !query.available_filters.has_key?('cf_3')
33 end
33 end
34
34
35 def test_system_shared_versions_should_be_available_in_global_queries
35 def test_system_shared_versions_should_be_available_in_global_queries
36 Version.find(2).update_attribute :sharing, 'system'
36 Version.find(2).update_attribute :sharing, 'system'
37 query = Query.new(:project => nil, :name => '_')
37 query = Query.new(:project => nil, :name => '_')
38 assert query.available_filters.has_key?('fixed_version_id')
38 assert query.available_filters.has_key?('fixed_version_id')
39 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
39 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
40 end
40 end
41
41
42 def test_project_filter_in_global_queries
42 def test_project_filter_in_global_queries
43 query = Query.new(:project => nil, :name => '_')
43 query = Query.new(:project => nil, :name => '_')
44 project_filter = query.available_filters["project_id"]
44 project_filter = query.available_filters["project_id"]
45 assert_not_nil project_filter
45 assert_not_nil project_filter
46 project_ids = project_filter[:values].map{|p| p[1]}
46 project_ids = project_filter[:values].map{|p| p[1]}
47 assert project_ids.include?("1") #public project
47 assert project_ids.include?("1") #public project
48 assert !project_ids.include?("2") #private project user cannot see
48 assert !project_ids.include?("2") #private project user cannot see
49 end
49 end
50
50
51 def find_issues_with_query(query)
51 def find_issues_with_query(query)
52 Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
52 Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
53 query.statement
53 query.statement
54 ).all
54 ).all
55 end
55 end
56
56
57 def assert_find_issues_with_query_is_successful(query)
57 def assert_find_issues_with_query_is_successful(query)
58 assert_nothing_raised do
58 assert_nothing_raised do
59 find_issues_with_query(query)
59 find_issues_with_query(query)
60 end
60 end
61 end
61 end
62
62
63 def assert_query_statement_includes(query, condition)
63 def assert_query_statement_includes(query, condition)
64 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
64 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
65 end
65 end
66
66
67 def assert_query_result(expected, query)
67 def assert_query_result(expected, query)
68 assert_nothing_raised do
68 assert_nothing_raised do
69 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
69 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
70 assert_equal expected.size, query.issue_count
70 assert_equal expected.size, query.issue_count
71 end
71 end
72 end
72 end
73
73
74 def test_query_should_allow_shared_versions_for_a_project_query
74 def test_query_should_allow_shared_versions_for_a_project_query
75 subproject_version = Version.find(4)
75 subproject_version = Version.find(4)
76 query = Query.new(:project => Project.find(1), :name => '_')
76 query = Query.new(:project => Project.find(1), :name => '_')
77 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
77 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
78
78
79 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
79 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
80 end
80 end
81
81
82 def test_query_with_multiple_custom_fields
82 def test_query_with_multiple_custom_fields
83 query = Query.find(1)
83 query = Query.find(1)
84 assert query.valid?
84 assert query.valid?
85 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
85 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
86 issues = find_issues_with_query(query)
86 issues = find_issues_with_query(query)
87 assert_equal 1, issues.length
87 assert_equal 1, issues.length
88 assert_equal Issue.find(3), issues.first
88 assert_equal Issue.find(3), issues.first
89 end
89 end
90
90
91 def test_operator_none
91 def test_operator_none
92 query = Query.new(:project => Project.find(1), :name => '_')
92 query = Query.new(:project => Project.find(1), :name => '_')
93 query.add_filter('fixed_version_id', '!*', [''])
93 query.add_filter('fixed_version_id', '!*', [''])
94 query.add_filter('cf_1', '!*', [''])
94 query.add_filter('cf_1', '!*', [''])
95 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
95 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
96 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
96 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
97 find_issues_with_query(query)
97 find_issues_with_query(query)
98 end
98 end
99
99
100 def test_operator_none_for_integer
100 def test_operator_none_for_integer
101 query = Query.new(:project => Project.find(1), :name => '_')
101 query = Query.new(:project => Project.find(1), :name => '_')
102 query.add_filter('estimated_hours', '!*', [''])
102 query.add_filter('estimated_hours', '!*', [''])
103 issues = find_issues_with_query(query)
103 issues = find_issues_with_query(query)
104 assert !issues.empty?
104 assert !issues.empty?
105 assert issues.all? {|i| !i.estimated_hours}
105 assert issues.all? {|i| !i.estimated_hours}
106 end
106 end
107
107
108 def test_operator_none_for_date
108 def test_operator_none_for_date
109 query = Query.new(:project => Project.find(1), :name => '_')
109 query = Query.new(:project => Project.find(1), :name => '_')
110 query.add_filter('start_date', '!*', [''])
110 query.add_filter('start_date', '!*', [''])
111 issues = find_issues_with_query(query)
111 issues = find_issues_with_query(query)
112 assert !issues.empty?
112 assert !issues.empty?
113 assert issues.all? {|i| i.start_date.nil?}
113 assert issues.all? {|i| i.start_date.nil?}
114 end
114 end
115
115
116 def test_operator_none_for_string_custom_field
116 def test_operator_none_for_string_custom_field
117 query = Query.new(:project => Project.find(1), :name => '_')
117 query = Query.new(:project => Project.find(1), :name => '_')
118 query.add_filter('cf_2', '!*', [''])
118 query.add_filter('cf_2', '!*', [''])
119 assert query.has_filter?('cf_2')
119 assert query.has_filter?('cf_2')
120 issues = find_issues_with_query(query)
120 issues = find_issues_with_query(query)
121 assert !issues.empty?
121 assert !issues.empty?
122 assert issues.all? {|i| i.custom_field_value(2).blank?}
122 assert issues.all? {|i| i.custom_field_value(2).blank?}
123 end
123 end
124
124
125 def test_operator_all
125 def test_operator_all
126 query = Query.new(:project => Project.find(1), :name => '_')
126 query = Query.new(:project => Project.find(1), :name => '_')
127 query.add_filter('fixed_version_id', '*', [''])
127 query.add_filter('fixed_version_id', '*', [''])
128 query.add_filter('cf_1', '*', [''])
128 query.add_filter('cf_1', '*', [''])
129 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
129 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
130 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
130 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
131 find_issues_with_query(query)
131 find_issues_with_query(query)
132 end
132 end
133
133
134 def test_operator_all_for_date
134 def test_operator_all_for_date
135 query = Query.new(:project => Project.find(1), :name => '_')
135 query = Query.new(:project => Project.find(1), :name => '_')
136 query.add_filter('start_date', '*', [''])
136 query.add_filter('start_date', '*', [''])
137 issues = find_issues_with_query(query)
137 issues = find_issues_with_query(query)
138 assert !issues.empty?
138 assert !issues.empty?
139 assert issues.all? {|i| i.start_date.present?}
139 assert issues.all? {|i| i.start_date.present?}
140 end
140 end
141
141
142 def test_operator_all_for_string_custom_field
142 def test_operator_all_for_string_custom_field
143 query = Query.new(:project => Project.find(1), :name => '_')
143 query = Query.new(:project => Project.find(1), :name => '_')
144 query.add_filter('cf_2', '*', [''])
144 query.add_filter('cf_2', '*', [''])
145 assert query.has_filter?('cf_2')
145 assert query.has_filter?('cf_2')
146 issues = find_issues_with_query(query)
146 issues = find_issues_with_query(query)
147 assert !issues.empty?
147 assert !issues.empty?
148 assert issues.all? {|i| i.custom_field_value(2).present?}
148 assert issues.all? {|i| i.custom_field_value(2).present?}
149 end
149 end
150
150
151 def test_numeric_filter_should_not_accept_non_numeric_values
151 def test_numeric_filter_should_not_accept_non_numeric_values
152 query = Query.new(:name => '_')
152 query = Query.new(:name => '_')
153 query.add_filter('estimated_hours', '=', ['a'])
153 query.add_filter('estimated_hours', '=', ['a'])
154
154
155 assert query.has_filter?('estimated_hours')
155 assert query.has_filter?('estimated_hours')
156 assert !query.valid?
156 assert !query.valid?
157 end
157 end
158
158
159 def test_operator_is_on_float
159 def test_operator_is_on_float
160 Issue.update_all("estimated_hours = 171.2", "id=2")
160 Issue.update_all("estimated_hours = 171.2", "id=2")
161
161
162 query = Query.new(:name => '_')
162 query = Query.new(:name => '_')
163 query.add_filter('estimated_hours', '=', ['171.20'])
163 query.add_filter('estimated_hours', '=', ['171.20'])
164 issues = find_issues_with_query(query)
164 issues = find_issues_with_query(query)
165 assert_equal 1, issues.size
165 assert_equal 1, issues.size
166 assert_equal 2, issues.first.id
166 assert_equal 2, issues.first.id
167 end
167 end
168
168
169 def test_operator_is_on_integer_custom_field
169 def test_operator_is_on_integer_custom_field
170 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
170 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
171 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
171 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
172 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
172 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
173 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
173 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
174
174
175 query = Query.new(:name => '_')
175 query = Query.new(:name => '_')
176 query.add_filter("cf_#{f.id}", '=', ['12'])
176 query.add_filter("cf_#{f.id}", '=', ['12'])
177 issues = find_issues_with_query(query)
177 issues = find_issues_with_query(query)
178 assert_equal 1, issues.size
178 assert_equal 1, issues.size
179 assert_equal 2, issues.first.id
179 assert_equal 2, issues.first.id
180 end
180 end
181
181
182 def test_operator_is_on_integer_custom_field_should_accept_negative_value
182 def test_operator_is_on_integer_custom_field_should_accept_negative_value
183 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
183 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
184 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
184 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
185 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
185 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
186 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
186 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
187
187
188 query = Query.new(:name => '_')
188 query = Query.new(:name => '_')
189 query.add_filter("cf_#{f.id}", '=', ['-12'])
189 query.add_filter("cf_#{f.id}", '=', ['-12'])
190 assert query.valid?
190 assert query.valid?
191 issues = find_issues_with_query(query)
191 issues = find_issues_with_query(query)
192 assert_equal 1, issues.size
192 assert_equal 1, issues.size
193 assert_equal 2, issues.first.id
193 assert_equal 2, issues.first.id
194 end
194 end
195
195
196 def test_operator_is_on_float_custom_field
196 def test_operator_is_on_float_custom_field
197 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
197 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
198 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
198 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
199 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
199 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
200 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
200 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
201
201
202 query = Query.new(:name => '_')
202 query = Query.new(:name => '_')
203 query.add_filter("cf_#{f.id}", '=', ['12.7'])
203 query.add_filter("cf_#{f.id}", '=', ['12.7'])
204 issues = find_issues_with_query(query)
204 issues = find_issues_with_query(query)
205 assert_equal 1, issues.size
205 assert_equal 1, issues.size
206 assert_equal 2, issues.first.id
206 assert_equal 2, issues.first.id
207 end
207 end
208
208
209 def test_operator_is_on_float_custom_field_should_accept_negative_value
209 def test_operator_is_on_float_custom_field_should_accept_negative_value
210 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
210 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
211 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
211 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
212 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
212 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
213 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
213 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
214
214
215 query = Query.new(:name => '_')
215 query = Query.new(:name => '_')
216 query.add_filter("cf_#{f.id}", '=', ['-12.7'])
216 query.add_filter("cf_#{f.id}", '=', ['-12.7'])
217 assert query.valid?
217 assert query.valid?
218 issues = find_issues_with_query(query)
218 issues = find_issues_with_query(query)
219 assert_equal 1, issues.size
219 assert_equal 1, issues.size
220 assert_equal 2, issues.first.id
220 assert_equal 2, issues.first.id
221 end
221 end
222
222
223 def test_operator_is_on_multi_list_custom_field
223 def test_operator_is_on_multi_list_custom_field
224 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
224 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
225 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
225 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
226 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
226 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
227 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
227 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
228 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
228 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
229
229
230 query = Query.new(:name => '_')
230 query = Query.new(:name => '_')
231 query.add_filter("cf_#{f.id}", '=', ['value1'])
231 query.add_filter("cf_#{f.id}", '=', ['value1'])
232 issues = find_issues_with_query(query)
232 issues = find_issues_with_query(query)
233 assert_equal [1, 3], issues.map(&:id).sort
233 assert_equal [1, 3], issues.map(&:id).sort
234
234
235 query = Query.new(:name => '_')
235 query = Query.new(:name => '_')
236 query.add_filter("cf_#{f.id}", '=', ['value2'])
236 query.add_filter("cf_#{f.id}", '=', ['value2'])
237 issues = find_issues_with_query(query)
237 issues = find_issues_with_query(query)
238 assert_equal [1], issues.map(&:id).sort
238 assert_equal [1], issues.map(&:id).sort
239 end
239 end
240
240
241 def test_operator_is_not_on_multi_list_custom_field
241 def test_operator_is_not_on_multi_list_custom_field
242 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
242 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
243 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
243 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
244 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
244 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
245 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
245 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
246 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
246 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
247
247
248 query = Query.new(:name => '_')
248 query = Query.new(:name => '_')
249 query.add_filter("cf_#{f.id}", '!', ['value1'])
249 query.add_filter("cf_#{f.id}", '!', ['value1'])
250 issues = find_issues_with_query(query)
250 issues = find_issues_with_query(query)
251 assert !issues.map(&:id).include?(1)
251 assert !issues.map(&:id).include?(1)
252 assert !issues.map(&:id).include?(3)
252 assert !issues.map(&:id).include?(3)
253
253
254 query = Query.new(:name => '_')
254 query = Query.new(:name => '_')
255 query.add_filter("cf_#{f.id}", '!', ['value2'])
255 query.add_filter("cf_#{f.id}", '!', ['value2'])
256 issues = find_issues_with_query(query)
256 issues = find_issues_with_query(query)
257 assert !issues.map(&:id).include?(1)
257 assert !issues.map(&:id).include?(1)
258 assert issues.map(&:id).include?(3)
258 assert issues.map(&:id).include?(3)
259 end
259 end
260
260
261 def test_operator_is_on_is_private_field
261 def test_operator_is_on_is_private_field
262 # is_private filter only available for those who can set issues private
262 # is_private filter only available for those who can set issues private
263 User.current = User.find(2)
263 User.current = User.find(2)
264
264
265 query = Query.new(:name => '_')
265 query = Query.new(:name => '_')
266 assert query.available_filters.key?('is_private')
266 assert query.available_filters.key?('is_private')
267
267
268 query.add_filter("is_private", '=', ['1'])
268 query.add_filter("is_private", '=', ['1'])
269 issues = find_issues_with_query(query)
269 issues = find_issues_with_query(query)
270 assert issues.any?
270 assert issues.any?
271 assert_nil issues.detect {|issue| !issue.is_private?}
271 assert_nil issues.detect {|issue| !issue.is_private?}
272 ensure
272 ensure
273 User.current = nil
273 User.current = nil
274 end
274 end
275
275
276 def test_operator_is_not_on_is_private_field
276 def test_operator_is_not_on_is_private_field
277 # is_private filter only available for those who can set issues private
277 # is_private filter only available for those who can set issues private
278 User.current = User.find(2)
278 User.current = User.find(2)
279
279
280 query = Query.new(:name => '_')
280 query = Query.new(:name => '_')
281 assert query.available_filters.key?('is_private')
281 assert query.available_filters.key?('is_private')
282
282
283 query.add_filter("is_private", '!', ['1'])
283 query.add_filter("is_private", '!', ['1'])
284 issues = find_issues_with_query(query)
284 issues = find_issues_with_query(query)
285 assert issues.any?
285 assert issues.any?
286 assert_nil issues.detect {|issue| issue.is_private?}
286 assert_nil issues.detect {|issue| issue.is_private?}
287 ensure
287 ensure
288 User.current = nil
288 User.current = nil
289 end
289 end
290
290
291 def test_operator_greater_than
291 def test_operator_greater_than
292 query = Query.new(:project => Project.find(1), :name => '_')
292 query = Query.new(:project => Project.find(1), :name => '_')
293 query.add_filter('done_ratio', '>=', ['40'])
293 query.add_filter('done_ratio', '>=', ['40'])
294 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
294 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
295 find_issues_with_query(query)
295 find_issues_with_query(query)
296 end
296 end
297
297
298 def test_operator_greater_than_a_float
298 def test_operator_greater_than_a_float
299 query = Query.new(:project => Project.find(1), :name => '_')
299 query = Query.new(:project => Project.find(1), :name => '_')
300 query.add_filter('estimated_hours', '>=', ['40.5'])
300 query.add_filter('estimated_hours', '>=', ['40.5'])
301 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
301 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
302 find_issues_with_query(query)
302 find_issues_with_query(query)
303 end
303 end
304
304
305 def test_operator_greater_than_on_int_custom_field
305 def test_operator_greater_than_on_int_custom_field
306 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
306 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
307 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
307 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
308 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
308 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
309 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
309 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
310
310
311 query = Query.new(:project => Project.find(1), :name => '_')
311 query = Query.new(:project => Project.find(1), :name => '_')
312 query.add_filter("cf_#{f.id}", '>=', ['8'])
312 query.add_filter("cf_#{f.id}", '>=', ['8'])
313 issues = find_issues_with_query(query)
313 issues = find_issues_with_query(query)
314 assert_equal 1, issues.size
314 assert_equal 1, issues.size
315 assert_equal 2, issues.first.id
315 assert_equal 2, issues.first.id
316 end
316 end
317
317
318 def test_operator_lesser_than
318 def test_operator_lesser_than
319 query = Query.new(:project => Project.find(1), :name => '_')
319 query = Query.new(:project => Project.find(1), :name => '_')
320 query.add_filter('done_ratio', '<=', ['30'])
320 query.add_filter('done_ratio', '<=', ['30'])
321 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
321 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
322 find_issues_with_query(query)
322 find_issues_with_query(query)
323 end
323 end
324
324
325 def test_operator_lesser_than_on_custom_field
325 def test_operator_lesser_than_on_custom_field
326 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
326 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
327 query = Query.new(:project => Project.find(1), :name => '_')
327 query = Query.new(:project => Project.find(1), :name => '_')
328 query.add_filter("cf_#{f.id}", '<=', ['30'])
328 query.add_filter("cf_#{f.id}", '<=', ['30'])
329 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0")
329 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0")
330 find_issues_with_query(query)
330 find_issues_with_query(query)
331 end
331 end
332
332
333 def test_operator_between
333 def test_operator_between
334 query = Query.new(:project => Project.find(1), :name => '_')
334 query = Query.new(:project => Project.find(1), :name => '_')
335 query.add_filter('done_ratio', '><', ['30', '40'])
335 query.add_filter('done_ratio', '><', ['30', '40'])
336 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
336 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
337 find_issues_with_query(query)
337 find_issues_with_query(query)
338 end
338 end
339
339
340 def test_operator_between_on_custom_field
340 def test_operator_between_on_custom_field
341 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
341 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
342 query = Query.new(:project => Project.find(1), :name => '_')
342 query = Query.new(:project => Project.find(1), :name => '_')
343 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
343 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
344 assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement
344 assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement
345 find_issues_with_query(query)
345 find_issues_with_query(query)
346 end
346 end
347
347
348 def test_date_filter_should_not_accept_non_date_values
348 def test_date_filter_should_not_accept_non_date_values
349 query = Query.new(:name => '_')
349 query = Query.new(:name => '_')
350 query.add_filter('created_on', '=', ['a'])
350 query.add_filter('created_on', '=', ['a'])
351
351
352 assert query.has_filter?('created_on')
352 assert query.has_filter?('created_on')
353 assert !query.valid?
353 assert !query.valid?
354 end
354 end
355
355
356 def test_date_filter_should_not_accept_invalid_date_values
356 def test_date_filter_should_not_accept_invalid_date_values
357 query = Query.new(:name => '_')
357 query = Query.new(:name => '_')
358 query.add_filter('created_on', '=', ['2011-01-34'])
358 query.add_filter('created_on', '=', ['2011-01-34'])
359
359
360 assert query.has_filter?('created_on')
360 assert query.has_filter?('created_on')
361 assert !query.valid?
361 assert !query.valid?
362 end
362 end
363
363
364 def test_relative_date_filter_should_not_accept_non_integer_values
364 def test_relative_date_filter_should_not_accept_non_integer_values
365 query = Query.new(:name => '_')
365 query = Query.new(:name => '_')
366 query.add_filter('created_on', '>t-', ['a'])
366 query.add_filter('created_on', '>t-', ['a'])
367
367
368 assert query.has_filter?('created_on')
368 assert query.has_filter?('created_on')
369 assert !query.valid?
369 assert !query.valid?
370 end
370 end
371
371
372 def test_operator_date_equals
372 def test_operator_date_equals
373 query = Query.new(:name => '_')
373 query = Query.new(:name => '_')
374 query.add_filter('due_date', '=', ['2011-07-10'])
374 query.add_filter('due_date', '=', ['2011-07-10'])
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
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 find_issues_with_query(query)
376 find_issues_with_query(query)
377 end
377 end
378
378
379 def test_operator_date_lesser_than
379 def test_operator_date_lesser_than
380 query = Query.new(:name => '_')
380 query = Query.new(:name => '_')
381 query.add_filter('due_date', '<=', ['2011-07-10'])
381 query.add_filter('due_date', '<=', ['2011-07-10'])
382 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
382 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
383 find_issues_with_query(query)
383 find_issues_with_query(query)
384 end
384 end
385
385
386 def test_operator_date_greater_than
386 def test_operator_date_greater_than
387 query = Query.new(:name => '_')
387 query = Query.new(:name => '_')
388 query.add_filter('due_date', '>=', ['2011-07-10'])
388 query.add_filter('due_date', '>=', ['2011-07-10'])
389 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
389 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
390 find_issues_with_query(query)
390 find_issues_with_query(query)
391 end
391 end
392
392
393 def test_operator_date_between
393 def test_operator_date_between
394 query = Query.new(:name => '_')
394 query = Query.new(:name => '_')
395 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
395 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
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
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 find_issues_with_query(query)
397 find_issues_with_query(query)
398 end
398 end
399
399
400 def test_operator_in_more_than
400 def test_operator_in_more_than
401 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
401 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
402 query = Query.new(:project => Project.find(1), :name => '_')
402 query = Query.new(:project => Project.find(1), :name => '_')
403 query.add_filter('due_date', '>t+', ['15'])
403 query.add_filter('due_date', '>t+', ['15'])
404 issues = find_issues_with_query(query)
404 issues = find_issues_with_query(query)
405 assert !issues.empty?
405 assert !issues.empty?
406 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
406 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
407 end
407 end
408
408
409 def test_operator_in_less_than
409 def test_operator_in_less_than
410 query = Query.new(:project => Project.find(1), :name => '_')
410 query = Query.new(:project => Project.find(1), :name => '_')
411 query.add_filter('due_date', '<t+', ['15'])
411 query.add_filter('due_date', '<t+', ['15'])
412 issues = find_issues_with_query(query)
412 issues = find_issues_with_query(query)
413 assert !issues.empty?
413 assert !issues.empty?
414 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
414 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
415 end
415 end
416
416
417 def test_operator_less_than_ago
417 def test_operator_less_than_ago
418 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
418 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
419 query = Query.new(:project => Project.find(1), :name => '_')
419 query = Query.new(:project => Project.find(1), :name => '_')
420 query.add_filter('due_date', '>t-', ['3'])
420 query.add_filter('due_date', '>t-', ['3'])
421 issues = find_issues_with_query(query)
421 issues = find_issues_with_query(query)
422 assert !issues.empty?
422 assert !issues.empty?
423 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
423 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
424 end
424 end
425
425
426 def test_operator_more_than_ago
426 def test_operator_more_than_ago
427 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
427 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
428 query = Query.new(:project => Project.find(1), :name => '_')
428 query = Query.new(:project => Project.find(1), :name => '_')
429 query.add_filter('due_date', '<t-', ['10'])
429 query.add_filter('due_date', '<t-', ['10'])
430 assert query.statement.include?("#{Issue.table_name}.due_date <=")
430 assert query.statement.include?("#{Issue.table_name}.due_date <=")
431 issues = find_issues_with_query(query)
431 issues = find_issues_with_query(query)
432 assert !issues.empty?
432 assert !issues.empty?
433 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
433 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
434 end
434 end
435
435
436 def test_operator_in
436 def test_operator_in
437 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
437 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
438 query = Query.new(:project => Project.find(1), :name => '_')
438 query = Query.new(:project => Project.find(1), :name => '_')
439 query.add_filter('due_date', 't+', ['2'])
439 query.add_filter('due_date', 't+', ['2'])
440 issues = find_issues_with_query(query)
440 issues = find_issues_with_query(query)
441 assert !issues.empty?
441 assert !issues.empty?
442 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
442 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
443 end
443 end
444
444
445 def test_operator_ago
445 def test_operator_ago
446 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
446 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
447 query = Query.new(:project => Project.find(1), :name => '_')
447 query = Query.new(:project => Project.find(1), :name => '_')
448 query.add_filter('due_date', 't-', ['3'])
448 query.add_filter('due_date', 't-', ['3'])
449 issues = find_issues_with_query(query)
449 issues = find_issues_with_query(query)
450 assert !issues.empty?
450 assert !issues.empty?
451 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
451 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
452 end
452 end
453
453
454 def test_operator_today
454 def test_operator_today
455 query = Query.new(:project => Project.find(1), :name => '_')
455 query = Query.new(:project => Project.find(1), :name => '_')
456 query.add_filter('due_date', 't', [''])
456 query.add_filter('due_date', 't', [''])
457 issues = find_issues_with_query(query)
457 issues = find_issues_with_query(query)
458 assert !issues.empty?
458 assert !issues.empty?
459 issues.each {|issue| assert_equal Date.today, issue.due_date}
459 issues.each {|issue| assert_equal Date.today, issue.due_date}
460 end
460 end
461
461
462 def test_operator_this_week_on_date
462 def test_operator_this_week_on_date
463 query = Query.new(:project => Project.find(1), :name => '_')
463 query = Query.new(:project => Project.find(1), :name => '_')
464 query.add_filter('due_date', 'w', [''])
464 query.add_filter('due_date', 'w', [''])
465 find_issues_with_query(query)
465 find_issues_with_query(query)
466 end
466 end
467
467
468 def test_operator_this_week_on_datetime
468 def test_operator_this_week_on_datetime
469 query = Query.new(:project => Project.find(1), :name => '_')
469 query = Query.new(:project => Project.find(1), :name => '_')
470 query.add_filter('created_on', 'w', [''])
470 query.add_filter('created_on', 'w', [''])
471 find_issues_with_query(query)
471 find_issues_with_query(query)
472 end
472 end
473
473
474 def test_operator_contains
474 def test_operator_contains
475 query = Query.new(:project => Project.find(1), :name => '_')
475 query = Query.new(:project => Project.find(1), :name => '_')
476 query.add_filter('subject', '~', ['uNable'])
476 query.add_filter('subject', '~', ['uNable'])
477 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
477 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
478 result = find_issues_with_query(query)
478 result = find_issues_with_query(query)
479 assert result.empty?
479 assert result.empty?
480 result.each {|issue| assert issue.subject.downcase.include?('unable') }
480 result.each {|issue| assert issue.subject.downcase.include?('unable') }
481 end
481 end
482
482
483 def test_range_for_this_week_with_week_starting_on_monday
483 def test_range_for_this_week_with_week_starting_on_monday
484 I18n.locale = :fr
484 I18n.locale = :fr
485 assert_equal '1', I18n.t(:general_first_day_of_week)
485 assert_equal '1', I18n.t(:general_first_day_of_week)
486
486
487 Date.stubs(:today).returns(Date.parse('2011-04-29'))
487 Date.stubs(:today).returns(Date.parse('2011-04-29'))
488
488
489 query = Query.new(:project => Project.find(1), :name => '_')
489 query = Query.new(:project => Project.find(1), :name => '_')
490 query.add_filter('due_date', 'w', [''])
490 query.add_filter('due_date', 'w', [''])
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}"
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 I18n.locale = :en
492 I18n.locale = :en
493 end
493 end
494
494
495 def test_range_for_this_week_with_week_starting_on_sunday
495 def test_range_for_this_week_with_week_starting_on_sunday
496 I18n.locale = :en
496 I18n.locale = :en
497 assert_equal '7', I18n.t(:general_first_day_of_week)
497 assert_equal '7', I18n.t(:general_first_day_of_week)
498
498
499 Date.stubs(:today).returns(Date.parse('2011-04-29'))
499 Date.stubs(:today).returns(Date.parse('2011-04-29'))
500
500
501 query = Query.new(:project => Project.find(1), :name => '_')
501 query = Query.new(:project => Project.find(1), :name => '_')
502 query.add_filter('due_date', 'w', [''])
502 query.add_filter('due_date', 'w', [''])
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}"
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 end
504 end
505
505
506 def test_operator_does_not_contains
506 def test_operator_does_not_contains
507 query = Query.new(:project => Project.find(1), :name => '_')
507 query = Query.new(:project => Project.find(1), :name => '_')
508 query.add_filter('subject', '!~', ['uNable'])
508 query.add_filter('subject', '!~', ['uNable'])
509 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
509 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
510 find_issues_with_query(query)
510 find_issues_with_query(query)
511 end
511 end
512
512
513 def test_filter_assigned_to_me
513 def test_filter_assigned_to_me
514 user = User.find(2)
514 user = User.find(2)
515 group = Group.find(10)
515 group = Group.find(10)
516 User.current = user
516 User.current = user
517 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
517 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
518 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
518 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
519 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
519 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
520 group.users << user
520 group.users << user
521
521
522 query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
522 query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
523 result = query.issues
523 result = query.issues
524 assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id)
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 assert result.include?(i1)
526 assert result.include?(i1)
527 assert result.include?(i2)
527 assert result.include?(i2)
528 assert !result.include?(i3)
528 assert !result.include?(i3)
529 end
529 end
530
530
531 def test_user_custom_field_filtered_on_me
531 def test_user_custom_field_filtered_on_me
532 User.current = User.find(2)
532 User.current = User.find(2)
533 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
533 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
534 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
534 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
535 issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
535 issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
536
536
537 query = Query.new(:name => '_', :project => Project.find(1))
537 query = Query.new(:name => '_', :project => Project.find(1))
538 filter = query.available_filters["cf_#{cf.id}"]
538 filter = query.available_filters["cf_#{cf.id}"]
539 assert_not_nil filter
539 assert_not_nil filter
540 assert_include 'me', filter[:values].map{|v| v[1]}
540 assert_include 'me', filter[:values].map{|v| v[1]}
541
541
542 query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
542 query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
543 result = query.issues
543 result = query.issues
544 assert_equal 1, result.size
544 assert_equal 1, result.size
545 assert_equal issue1, result.first
545 assert_equal issue1, result.first
546 end
546 end
547
547
548 def test_filter_my_projects
548 def test_filter_my_projects
549 User.current = User.find(2)
549 User.current = User.find(2)
550 query = Query.new(:name => '_')
550 query = Query.new(:name => '_')
551 filter = query.available_filters['project_id']
551 filter = query.available_filters['project_id']
552 assert_not_nil filter
552 assert_not_nil filter
553 assert_include 'mine', filter[:values].map{|v| v[1]}
553 assert_include 'mine', filter[:values].map{|v| v[1]}
554
554
555 query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
555 query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
556 result = query.issues
556 result = query.issues
557 assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
557 assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
558 end
558 end
559
559
560 def test_filter_watched_issues
560 def test_filter_watched_issues
561 User.current = User.find(1)
561 User.current = User.find(1)
562 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
562 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
563 result = find_issues_with_query(query)
563 result = find_issues_with_query(query)
564 assert_not_nil result
564 assert_not_nil result
565 assert !result.empty?
565 assert !result.empty?
566 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
566 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
567 User.current = nil
567 User.current = nil
568 end
568 end
569
569
570 def test_filter_unwatched_issues
570 def test_filter_unwatched_issues
571 User.current = User.find(1)
571 User.current = User.find(1)
572 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
572 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
573 result = find_issues_with_query(query)
573 result = find_issues_with_query(query)
574 assert_not_nil result
574 assert_not_nil result
575 assert !result.empty?
575 assert !result.empty?
576 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
576 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
577 User.current = nil
577 User.current = nil
578 end
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 def test_statement_should_be_nil_with_no_filters
625 def test_statement_should_be_nil_with_no_filters
581 q = Query.new(:name => '_')
626 q = Query.new(:name => '_')
582 q.filters = {}
627 q.filters = {}
583
628
584 assert q.valid?
629 assert q.valid?
585 assert_nil q.statement
630 assert_nil q.statement
586 end
631 end
587
632
588 def test_default_columns
633 def test_default_columns
589 q = Query.new
634 q = Query.new
590 assert !q.columns.empty?
635 assert !q.columns.empty?
591 end
636 end
592
637
593 def test_set_column_names
638 def test_set_column_names
594 q = Query.new
639 q = Query.new
595 q.column_names = ['tracker', :subject, '', 'unknonw_column']
640 q.column_names = ['tracker', :subject, '', 'unknonw_column']
596 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
641 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
597 c = q.columns.first
642 c = q.columns.first
598 assert q.has_column?(c)
643 assert q.has_column?(c)
599 end
644 end
600
645
601 def test_query_should_preload_spent_hours
646 def test_query_should_preload_spent_hours
602 q = Query.new(:name => '_', :column_names => [:subject, :spent_hours])
647 q = Query.new(:name => '_', :column_names => [:subject, :spent_hours])
603 assert q.has_column?(:spent_hours)
648 assert q.has_column?(:spent_hours)
604 issues = q.issues
649 issues = q.issues
605 assert_not_nil issues.first.instance_variable_get("@spent_hours")
650 assert_not_nil issues.first.instance_variable_get("@spent_hours")
606 end
651 end
607
652
608 def test_groupable_columns_should_include_custom_fields
653 def test_groupable_columns_should_include_custom_fields
609 q = Query.new
654 q = Query.new
610 column = q.groupable_columns.detect {|c| c.name == :cf_1}
655 column = q.groupable_columns.detect {|c| c.name == :cf_1}
611 assert_not_nil column
656 assert_not_nil column
612 assert_kind_of QueryCustomFieldColumn, column
657 assert_kind_of QueryCustomFieldColumn, column
613 end
658 end
614
659
615 def test_groupable_columns_should_not_include_multi_custom_fields
660 def test_groupable_columns_should_not_include_multi_custom_fields
616 field = CustomField.find(1)
661 field = CustomField.find(1)
617 field.update_attribute :multiple, true
662 field.update_attribute :multiple, true
618
663
619 q = Query.new
664 q = Query.new
620 column = q.groupable_columns.detect {|c| c.name == :cf_1}
665 column = q.groupable_columns.detect {|c| c.name == :cf_1}
621 assert_nil column
666 assert_nil column
622 end
667 end
623
668
624 def test_groupable_columns_should_include_user_custom_fields
669 def test_groupable_columns_should_include_user_custom_fields
625 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
670 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
626
671
627 q = Query.new
672 q = Query.new
628 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
673 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
629 end
674 end
630
675
631 def test_groupable_columns_should_include_version_custom_fields
676 def test_groupable_columns_should_include_version_custom_fields
632 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
677 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
633
678
634 q = Query.new
679 q = Query.new
635 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
680 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
636 end
681 end
637
682
638 def test_grouped_with_valid_column
683 def test_grouped_with_valid_column
639 q = Query.new(:group_by => 'status')
684 q = Query.new(:group_by => 'status')
640 assert q.grouped?
685 assert q.grouped?
641 assert_not_nil q.group_by_column
686 assert_not_nil q.group_by_column
642 assert_equal :status, q.group_by_column.name
687 assert_equal :status, q.group_by_column.name
643 assert_not_nil q.group_by_statement
688 assert_not_nil q.group_by_statement
644 assert_equal 'status', q.group_by_statement
689 assert_equal 'status', q.group_by_statement
645 end
690 end
646
691
647 def test_grouped_with_invalid_column
692 def test_grouped_with_invalid_column
648 q = Query.new(:group_by => 'foo')
693 q = Query.new(:group_by => 'foo')
649 assert !q.grouped?
694 assert !q.grouped?
650 assert_nil q.group_by_column
695 assert_nil q.group_by_column
651 assert_nil q.group_by_statement
696 assert_nil q.group_by_statement
652 end
697 end
653
698
654 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
699 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
655 with_settings :user_format => 'lastname_coma_firstname' do
700 with_settings :user_format => 'lastname_coma_firstname' do
656 q = Query.new
701 q = Query.new
657 assert q.sortable_columns.has_key?('assigned_to')
702 assert q.sortable_columns.has_key?('assigned_to')
658 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
703 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
659 end
704 end
660 end
705 end
661
706
662 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
707 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
663 with_settings :user_format => 'lastname_coma_firstname' do
708 with_settings :user_format => 'lastname_coma_firstname' do
664 q = Query.new
709 q = Query.new
665 assert q.sortable_columns.has_key?('author')
710 assert q.sortable_columns.has_key?('author')
666 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
711 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
667 end
712 end
668 end
713 end
669
714
670 def test_sortable_columns_should_include_custom_field
715 def test_sortable_columns_should_include_custom_field
671 q = Query.new
716 q = Query.new
672 assert q.sortable_columns['cf_1']
717 assert q.sortable_columns['cf_1']
673 end
718 end
674
719
675 def test_sortable_columns_should_not_include_multi_custom_field
720 def test_sortable_columns_should_not_include_multi_custom_field
676 field = CustomField.find(1)
721 field = CustomField.find(1)
677 field.update_attribute :multiple, true
722 field.update_attribute :multiple, true
678
723
679 q = Query.new
724 q = Query.new
680 assert !q.sortable_columns['cf_1']
725 assert !q.sortable_columns['cf_1']
681 end
726 end
682
727
683 def test_default_sort
728 def test_default_sort
684 q = Query.new
729 q = Query.new
685 assert_equal [], q.sort_criteria
730 assert_equal [], q.sort_criteria
686 end
731 end
687
732
688 def test_set_sort_criteria_with_hash
733 def test_set_sort_criteria_with_hash
689 q = Query.new
734 q = Query.new
690 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
735 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
691 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
736 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
692 end
737 end
693
738
694 def test_set_sort_criteria_with_array
739 def test_set_sort_criteria_with_array
695 q = Query.new
740 q = Query.new
696 q.sort_criteria = [['priority', 'desc'], 'tracker']
741 q.sort_criteria = [['priority', 'desc'], 'tracker']
697 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
742 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
698 end
743 end
699
744
700 def test_create_query_with_sort
745 def test_create_query_with_sort
701 q = Query.new(:name => 'Sorted')
746 q = Query.new(:name => 'Sorted')
702 q.sort_criteria = [['priority', 'desc'], 'tracker']
747 q.sort_criteria = [['priority', 'desc'], 'tracker']
703 assert q.save
748 assert q.save
704 q.reload
749 q.reload
705 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
750 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
706 end
751 end
707
752
708 def test_sort_by_string_custom_field_asc
753 def test_sort_by_string_custom_field_asc
709 q = Query.new
754 q = Query.new
710 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
755 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
711 assert c
756 assert c
712 assert c.sortable
757 assert c.sortable
713 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
758 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
714 q.statement
759 q.statement
715 ).order("#{c.sortable} ASC").all
760 ).order("#{c.sortable} ASC").all
716 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
761 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
717 assert !values.empty?
762 assert !values.empty?
718 assert_equal values.sort, values
763 assert_equal values.sort, values
719 end
764 end
720
765
721 def test_sort_by_string_custom_field_desc
766 def test_sort_by_string_custom_field_desc
722 q = Query.new
767 q = Query.new
723 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
768 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
724 assert c
769 assert c
725 assert c.sortable
770 assert c.sortable
726 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
771 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
727 q.statement
772 q.statement
728 ).order("#{c.sortable} DESC").all
773 ).order("#{c.sortable} DESC").all
729 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
774 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
730 assert !values.empty?
775 assert !values.empty?
731 assert_equal values.sort.reverse, values
776 assert_equal values.sort.reverse, values
732 end
777 end
733
778
734 def test_sort_by_float_custom_field_asc
779 def test_sort_by_float_custom_field_asc
735 q = Query.new
780 q = Query.new
736 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
781 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
737 assert c
782 assert c
738 assert c.sortable
783 assert c.sortable
739 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
784 issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
740 q.statement
785 q.statement
741 ).order("#{c.sortable} ASC").all
786 ).order("#{c.sortable} ASC").all
742 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
787 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
743 assert !values.empty?
788 assert !values.empty?
744 assert_equal values.sort, values
789 assert_equal values.sort, values
745 end
790 end
746
791
747 def test_invalid_query_should_raise_query_statement_invalid_error
792 def test_invalid_query_should_raise_query_statement_invalid_error
748 q = Query.new
793 q = Query.new
749 assert_raise Query::StatementInvalid do
794 assert_raise Query::StatementInvalid do
750 q.issues(:conditions => "foo = 1")
795 q.issues(:conditions => "foo = 1")
751 end
796 end
752 end
797 end
753
798
754 def test_issue_count
799 def test_issue_count
755 q = Query.new(:name => '_')
800 q = Query.new(:name => '_')
756 issue_count = q.issue_count
801 issue_count = q.issue_count
757 assert_equal q.issues.size, issue_count
802 assert_equal q.issues.size, issue_count
758 end
803 end
759
804
760 def test_issue_count_with_archived_issues
805 def test_issue_count_with_archived_issues
761 p = Project.generate! do |project|
806 p = Project.generate! do |project|
762 project.status = Project::STATUS_ARCHIVED
807 project.status = Project::STATUS_ARCHIVED
763 end
808 end
764 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
809 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
765 assert !i.visible?
810 assert !i.visible?
766
811
767 test_issue_count
812 test_issue_count
768 end
813 end
769
814
770 def test_issue_count_by_association_group
815 def test_issue_count_by_association_group
771 q = Query.new(:name => '_', :group_by => 'assigned_to')
816 q = Query.new(:name => '_', :group_by => 'assigned_to')
772 count_by_group = q.issue_count_by_group
817 count_by_group = q.issue_count_by_group
773 assert_kind_of Hash, count_by_group
818 assert_kind_of Hash, count_by_group
774 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
819 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
775 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
820 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
776 assert count_by_group.has_key?(User.find(3))
821 assert count_by_group.has_key?(User.find(3))
777 end
822 end
778
823
779 def test_issue_count_by_list_custom_field_group
824 def test_issue_count_by_list_custom_field_group
780 q = Query.new(:name => '_', :group_by => 'cf_1')
825 q = Query.new(:name => '_', :group_by => 'cf_1')
781 count_by_group = q.issue_count_by_group
826 count_by_group = q.issue_count_by_group
782 assert_kind_of Hash, count_by_group
827 assert_kind_of Hash, count_by_group
783 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
828 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
784 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
829 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
785 assert count_by_group.has_key?('MySQL')
830 assert count_by_group.has_key?('MySQL')
786 end
831 end
787
832
788 def test_issue_count_by_date_custom_field_group
833 def test_issue_count_by_date_custom_field_group
789 q = Query.new(:name => '_', :group_by => 'cf_8')
834 q = Query.new(:name => '_', :group_by => 'cf_8')
790 count_by_group = q.issue_count_by_group
835 count_by_group = q.issue_count_by_group
791 assert_kind_of Hash, count_by_group
836 assert_kind_of Hash, count_by_group
792 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
837 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
793 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
838 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
794 end
839 end
795
840
796 def test_issue_count_with_nil_group_only
841 def test_issue_count_with_nil_group_only
797 Issue.update_all("assigned_to_id = NULL")
842 Issue.update_all("assigned_to_id = NULL")
798
843
799 q = Query.new(:name => '_', :group_by => 'assigned_to')
844 q = Query.new(:name => '_', :group_by => 'assigned_to')
800 count_by_group = q.issue_count_by_group
845 count_by_group = q.issue_count_by_group
801 assert_kind_of Hash, count_by_group
846 assert_kind_of Hash, count_by_group
802 assert_equal 1, count_by_group.keys.size
847 assert_equal 1, count_by_group.keys.size
803 assert_nil count_by_group.keys.first
848 assert_nil count_by_group.keys.first
804 end
849 end
805
850
806 def test_issue_ids
851 def test_issue_ids
807 q = Query.new(:name => '_')
852 q = Query.new(:name => '_')
808 order = "issues.subject, issues.id"
853 order = "issues.subject, issues.id"
809 issues = q.issues(:order => order)
854 issues = q.issues(:order => order)
810 assert_equal issues.map(&:id), q.issue_ids(:order => order)
855 assert_equal issues.map(&:id), q.issue_ids(:order => order)
811 end
856 end
812
857
813 def test_label_for
858 def test_label_for
814 q = Query.new
859 q = Query.new
815 assert_equal 'Assignee', q.label_for('assigned_to_id')
860 assert_equal 'Assignee', q.label_for('assigned_to_id')
816 end
861 end
817
862
818 def test_editable_by
863 def test_editable_by
819 admin = User.find(1)
864 admin = User.find(1)
820 manager = User.find(2)
865 manager = User.find(2)
821 developer = User.find(3)
866 developer = User.find(3)
822
867
823 # Public query on project 1
868 # Public query on project 1
824 q = Query.find(1)
869 q = Query.find(1)
825 assert q.editable_by?(admin)
870 assert q.editable_by?(admin)
826 assert q.editable_by?(manager)
871 assert q.editable_by?(manager)
827 assert !q.editable_by?(developer)
872 assert !q.editable_by?(developer)
828
873
829 # Private query on project 1
874 # Private query on project 1
830 q = Query.find(2)
875 q = Query.find(2)
831 assert q.editable_by?(admin)
876 assert q.editable_by?(admin)
832 assert !q.editable_by?(manager)
877 assert !q.editable_by?(manager)
833 assert q.editable_by?(developer)
878 assert q.editable_by?(developer)
834
879
835 # Private query for all projects
880 # Private query for all projects
836 q = Query.find(3)
881 q = Query.find(3)
837 assert q.editable_by?(admin)
882 assert q.editable_by?(admin)
838 assert !q.editable_by?(manager)
883 assert !q.editable_by?(manager)
839 assert q.editable_by?(developer)
884 assert q.editable_by?(developer)
840
885
841 # Public query for all projects
886 # Public query for all projects
842 q = Query.find(4)
887 q = Query.find(4)
843 assert q.editable_by?(admin)
888 assert q.editable_by?(admin)
844 assert !q.editable_by?(manager)
889 assert !q.editable_by?(manager)
845 assert !q.editable_by?(developer)
890 assert !q.editable_by?(developer)
846 end
891 end
847
892
848 def test_visible_scope
893 def test_visible_scope
849 query_ids = Query.visible(User.anonymous).map(&:id)
894 query_ids = Query.visible(User.anonymous).map(&:id)
850
895
851 assert query_ids.include?(1), 'public query on public project was not visible'
896 assert query_ids.include?(1), 'public query on public project was not visible'
852 assert query_ids.include?(4), 'public query for all projects was not visible'
897 assert query_ids.include?(4), 'public query for all projects was not visible'
853 assert !query_ids.include?(2), 'private query on public project was visible'
898 assert !query_ids.include?(2), 'private query on public project was visible'
854 assert !query_ids.include?(3), 'private query for all projects was visible'
899 assert !query_ids.include?(3), 'private query for all projects was visible'
855 assert !query_ids.include?(7), 'public query on private project was visible'
900 assert !query_ids.include?(7), 'public query on private project was visible'
856 end
901 end
857
902
858 context "#available_filters" do
903 context "#available_filters" do
859 setup do
904 setup do
860 @query = Query.new(:name => "_")
905 @query = Query.new(:name => "_")
861 end
906 end
862
907
863 should "include users of visible projects in cross-project view" do
908 should "include users of visible projects in cross-project view" do
864 users = @query.available_filters["assigned_to_id"]
909 users = @query.available_filters["assigned_to_id"]
865 assert_not_nil users
910 assert_not_nil users
866 assert users[:values].map{|u|u[1]}.include?("3")
911 assert users[:values].map{|u|u[1]}.include?("3")
867 end
912 end
868
913
869 should "include users of subprojects" do
914 should "include users of subprojects" do
870 user1 = User.generate!
915 user1 = User.generate!
871 user2 = User.generate!
916 user2 = User.generate!
872 project = Project.find(1)
917 project = Project.find(1)
873 Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
918 Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
874 @query.project = project
919 @query.project = project
875
920
876 users = @query.available_filters["assigned_to_id"]
921 users = @query.available_filters["assigned_to_id"]
877 assert_not_nil users
922 assert_not_nil users
878 assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
923 assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
879 assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
924 assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
880 end
925 end
881
926
882 should "include visible projects in cross-project view" do
927 should "include visible projects in cross-project view" do
883 projects = @query.available_filters["project_id"]
928 projects = @query.available_filters["project_id"]
884 assert_not_nil projects
929 assert_not_nil projects
885 assert projects[:values].map{|u|u[1]}.include?("1")
930 assert projects[:values].map{|u|u[1]}.include?("1")
886 end
931 end
887
932
888 context "'member_of_group' filter" do
933 context "'member_of_group' filter" do
889 should "be present" do
934 should "be present" do
890 assert @query.available_filters.keys.include?("member_of_group")
935 assert @query.available_filters.keys.include?("member_of_group")
891 end
936 end
892
937
893 should "be an optional list" do
938 should "be an optional list" do
894 assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
939 assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
895 end
940 end
896
941
897 should "have a list of the groups as values" do
942 should "have a list of the groups as values" do
898 Group.destroy_all # No fixtures
943 Group.destroy_all # No fixtures
899 group1 = Group.generate!.reload
944 group1 = Group.generate!.reload
900 group2 = Group.generate!.reload
945 group2 = Group.generate!.reload
901
946
902 expected_group_list = [
947 expected_group_list = [
903 [group1.name, group1.id.to_s],
948 [group1.name, group1.id.to_s],
904 [group2.name, group2.id.to_s]
949 [group2.name, group2.id.to_s]
905 ]
950 ]
906 assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort
951 assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort
907 end
952 end
908
953
909 end
954 end
910
955
911 context "'assigned_to_role' filter" do
956 context "'assigned_to_role' filter" do
912 should "be present" do
957 should "be present" do
913 assert @query.available_filters.keys.include?("assigned_to_role")
958 assert @query.available_filters.keys.include?("assigned_to_role")
914 end
959 end
915
960
916 should "be an optional list" do
961 should "be an optional list" do
917 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
962 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
918 end
963 end
919
964
920 should "have a list of the Roles as values" do
965 should "have a list of the Roles as values" do
921 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
966 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
922 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
967 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
923 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
968 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
924 end
969 end
925
970
926 should "not include the built in Roles as values" do
971 should "not include the built in Roles as values" do
927 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
972 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
928 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
973 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
929 end
974 end
930
975
931 end
976 end
932
977
933 end
978 end
934
979
935 context "#statement" do
980 context "#statement" do
936 context "with 'member_of_group' filter" do
981 context "with 'member_of_group' filter" do
937 setup do
982 setup do
938 Group.destroy_all # No fixtures
983 Group.destroy_all # No fixtures
939 @user_in_group = User.generate!
984 @user_in_group = User.generate!
940 @second_user_in_group = User.generate!
985 @second_user_in_group = User.generate!
941 @user_in_group2 = User.generate!
986 @user_in_group2 = User.generate!
942 @user_not_in_group = User.generate!
987 @user_not_in_group = User.generate!
943
988
944 @group = Group.generate!.reload
989 @group = Group.generate!.reload
945 @group.users << @user_in_group
990 @group.users << @user_in_group
946 @group.users << @second_user_in_group
991 @group.users << @second_user_in_group
947
992
948 @group2 = Group.generate!.reload
993 @group2 = Group.generate!.reload
949 @group2.users << @user_in_group2
994 @group2.users << @user_in_group2
950
995
951 end
996 end
952
997
953 should "search assigned to for users in the group" do
998 should "search assigned to for users in the group" do
954 @query = Query.new(:name => '_')
999 @query = Query.new(:name => '_')
955 @query.add_filter('member_of_group', '=', [@group.id.to_s])
1000 @query.add_filter('member_of_group', '=', [@group.id.to_s])
956
1001
957 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
1002 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
958 assert_find_issues_with_query_is_successful @query
1003 assert_find_issues_with_query_is_successful @query
959 end
1004 end
960
1005
961 should "search not assigned to any group member (none)" do
1006 should "search not assigned to any group member (none)" do
962 @query = Query.new(:name => '_')
1007 @query = Query.new(:name => '_')
963 @query.add_filter('member_of_group', '!*', [''])
1008 @query.add_filter('member_of_group', '!*', [''])
964
1009
965 # Users not in a group
1010 # Users not in a group
966 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}')"
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 assert_find_issues_with_query_is_successful @query
1012 assert_find_issues_with_query_is_successful @query
968 end
1013 end
969
1014
970 should "search assigned to any group member (all)" do
1015 should "search assigned to any group member (all)" do
971 @query = Query.new(:name => '_')
1016 @query = Query.new(:name => '_')
972 @query.add_filter('member_of_group', '*', [''])
1017 @query.add_filter('member_of_group', '*', [''])
973
1018
974 # Only users in a group
1019 # Only users in a group
975 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}')"
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 assert_find_issues_with_query_is_successful @query
1021 assert_find_issues_with_query_is_successful @query
977 end
1022 end
978
1023
979 should "return an empty set with = empty group" do
1024 should "return an empty set with = empty group" do
980 @empty_group = Group.generate!
1025 @empty_group = Group.generate!
981 @query = Query.new(:name => '_')
1026 @query = Query.new(:name => '_')
982 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
1027 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
983
1028
984 assert_equal [], find_issues_with_query(@query)
1029 assert_equal [], find_issues_with_query(@query)
985 end
1030 end
986
1031
987 should "return issues with ! empty group" do
1032 should "return issues with ! empty group" do
988 @empty_group = Group.generate!
1033 @empty_group = Group.generate!
989 @query = Query.new(:name => '_')
1034 @query = Query.new(:name => '_')
990 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
1035 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
991
1036
992 assert_find_issues_with_query_is_successful @query
1037 assert_find_issues_with_query_is_successful @query
993 end
1038 end
994 end
1039 end
995
1040
996 context "with 'assigned_to_role' filter" do
1041 context "with 'assigned_to_role' filter" do
997 setup do
1042 setup do
998 @manager_role = Role.find_by_name('Manager')
1043 @manager_role = Role.find_by_name('Manager')
999 @developer_role = Role.find_by_name('Developer')
1044 @developer_role = Role.find_by_name('Developer')
1000
1045
1001 @project = Project.generate!
1046 @project = Project.generate!
1002 @manager = User.generate!
1047 @manager = User.generate!
1003 @developer = User.generate!
1048 @developer = User.generate!
1004 @boss = User.generate!
1049 @boss = User.generate!
1005 @guest = User.generate!
1050 @guest = User.generate!
1006 User.add_to_project(@manager, @project, @manager_role)
1051 User.add_to_project(@manager, @project, @manager_role)
1007 User.add_to_project(@developer, @project, @developer_role)
1052 User.add_to_project(@developer, @project, @developer_role)
1008 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
1053 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
1009
1054
1010 @issue1 = Issue.generate_for_project!(@project, :assigned_to_id => @manager.id)
1055 @issue1 = Issue.generate_for_project!(@project, :assigned_to_id => @manager.id)
1011 @issue2 = Issue.generate_for_project!(@project, :assigned_to_id => @developer.id)
1056 @issue2 = Issue.generate_for_project!(@project, :assigned_to_id => @developer.id)
1012 @issue3 = Issue.generate_for_project!(@project, :assigned_to_id => @boss.id)
1057 @issue3 = Issue.generate_for_project!(@project, :assigned_to_id => @boss.id)
1013 @issue4 = Issue.generate_for_project!(@project, :assigned_to_id => @guest.id)
1058 @issue4 = Issue.generate_for_project!(@project, :assigned_to_id => @guest.id)
1014 @issue5 = Issue.generate_for_project!(@project)
1059 @issue5 = Issue.generate_for_project!(@project)
1015 end
1060 end
1016
1061
1017 should "search assigned to for users with the Role" do
1062 should "search assigned to for users with the Role" do
1018 @query = Query.new(:name => '_', :project => @project)
1063 @query = Query.new(:name => '_', :project => @project)
1019 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1064 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1020
1065
1021 assert_query_result [@issue1, @issue3], @query
1066 assert_query_result [@issue1, @issue3], @query
1022 end
1067 end
1023
1068
1024 should "search assigned to for users with the Role on the issue project" do
1069 should "search assigned to for users with the Role on the issue project" do
1025 other_project = Project.generate!
1070 other_project = Project.generate!
1026 User.add_to_project(@developer, other_project, @manager_role)
1071 User.add_to_project(@developer, other_project, @manager_role)
1027
1072
1028 @query = Query.new(:name => '_', :project => @project)
1073 @query = Query.new(:name => '_', :project => @project)
1029 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1074 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1030
1075
1031 assert_query_result [@issue1, @issue3], @query
1076 assert_query_result [@issue1, @issue3], @query
1032 end
1077 end
1033
1078
1034 should "return an empty set with empty role" do
1079 should "return an empty set with empty role" do
1035 @empty_role = Role.generate!
1080 @empty_role = Role.generate!
1036 @query = Query.new(:name => '_', :project => @project)
1081 @query = Query.new(:name => '_', :project => @project)
1037 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
1082 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
1038
1083
1039 assert_query_result [], @query
1084 assert_query_result [], @query
1040 end
1085 end
1041
1086
1042 should "search assigned to for users without the Role" do
1087 should "search assigned to for users without the Role" do
1043 @query = Query.new(:name => '_', :project => @project)
1088 @query = Query.new(:name => '_', :project => @project)
1044 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
1089 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
1045
1090
1046 assert_query_result [@issue2, @issue4, @issue5], @query
1091 assert_query_result [@issue2, @issue4, @issue5], @query
1047 end
1092 end
1048
1093
1049 should "search assigned to for users not assigned to any Role (none)" do
1094 should "search assigned to for users not assigned to any Role (none)" do
1050 @query = Query.new(:name => '_', :project => @project)
1095 @query = Query.new(:name => '_', :project => @project)
1051 @query.add_filter('assigned_to_role', '!*', [''])
1096 @query.add_filter('assigned_to_role', '!*', [''])
1052
1097
1053 assert_query_result [@issue4, @issue5], @query
1098 assert_query_result [@issue4, @issue5], @query
1054 end
1099 end
1055
1100
1056 should "search assigned to for users assigned to any Role (all)" do
1101 should "search assigned to for users assigned to any Role (all)" do
1057 @query = Query.new(:name => '_', :project => @project)
1102 @query = Query.new(:name => '_', :project => @project)
1058 @query.add_filter('assigned_to_role', '*', [''])
1103 @query.add_filter('assigned_to_role', '*', [''])
1059
1104
1060 assert_query_result [@issue1, @issue2, @issue3], @query
1105 assert_query_result [@issue1, @issue2, @issue3], @query
1061 end
1106 end
1062
1107
1063 should "return issues with ! empty role" do
1108 should "return issues with ! empty role" do
1064 @empty_role = Role.generate!
1109 @empty_role = Role.generate!
1065 @query = Query.new(:name => '_', :project => @project)
1110 @query = Query.new(:name => '_', :project => @project)
1066 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
1111 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
1067
1112
1068 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
1113 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
1069 end
1114 end
1070 end
1115 end
1071 end
1116 end
1072
1117
1073 end
1118 end
General Comments 0
You need to be logged in to leave comments. Login now