@@ -1,1061 +1,1063 | |||||
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.sort {|a,b| a.to_s <=> b.to_s} : cv.first |
|
74 | cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : 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 | "=p" => :label_any_issues_in_project, |
|
117 | "=p" => :label_any_issues_in_project, | |
118 |
"=!p" => :label_any_issues_not_in_project |
|
118 | "=!p" => :label_any_issues_not_in_project, | |
|
119 | "!p" => :label_no_issues_in_project} | |||
119 |
|
120 | |||
120 | cattr_reader :operators |
|
121 | cattr_reader :operators | |
121 |
|
122 | |||
122 | @@operators_by_filter_type = { :list => [ "=", "!" ], |
|
123 | @@operators_by_filter_type = { :list => [ "=", "!" ], | |
123 | :list_status => [ "o", "=", "!", "c", "*" ], |
|
124 | :list_status => [ "o", "=", "!", "c", "*" ], | |
124 | :list_optional => [ "=", "!", "!*", "*" ], |
|
125 | :list_optional => [ "=", "!", "!*", "*" ], | |
125 | :list_subprojects => [ "*", "!*", "=" ], |
|
126 | :list_subprojects => [ "*", "!*", "=" ], | |
126 | :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-", "!*", "*" ], |
|
127 | :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-", "!*", "*" ], | |
127 | :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w", "!*", "*" ], |
|
128 | :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w", "!*", "*" ], | |
128 | :string => [ "=", "~", "!", "!~", "!*", "*" ], |
|
129 | :string => [ "=", "~", "!", "!~", "!*", "*" ], | |
129 | :text => [ "~", "!~", "!*", "*" ], |
|
130 | :text => [ "~", "!~", "!*", "*" ], | |
130 | :integer => [ "=", ">=", "<=", "><", "!*", "*" ], |
|
131 | :integer => [ "=", ">=", "<=", "><", "!*", "*" ], | |
131 | :float => [ "=", ">=", "<=", "><", "!*", "*" ], |
|
132 | :float => [ "=", ">=", "<=", "><", "!*", "*" ], | |
132 | :relation => ["=", "=p", "=!p", "!*", "*"]} |
|
133 | :relation => ["=", "=p", "=!p", "!p", "!*", "*"]} | |
133 |
|
134 | |||
134 | cattr_reader :operators_by_filter_type |
|
135 | cattr_reader :operators_by_filter_type | |
135 |
|
136 | |||
136 | @@available_columns = [ |
|
137 | @@available_columns = [ | |
137 | QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), |
|
138 | QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), | |
138 | QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), |
|
139 | QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), | |
139 | QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue), |
|
140 | QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue), | |
140 | QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true), |
|
141 | QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true), | |
141 | QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true), |
|
142 | QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true), | |
142 | QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"), |
|
143 | QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"), | |
143 | QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true), |
|
144 | QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true), | |
144 | QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), |
|
145 | QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), | |
145 | QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), |
|
146 | QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), | |
146 | QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true), |
|
147 | QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true), | |
147 | QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true), |
|
148 | QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true), | |
148 | QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), |
|
149 | QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), | |
149 | QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), |
|
150 | QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), | |
150 | QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), |
|
151 | QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), | |
151 | QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), |
|
152 | QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), | |
152 | QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), |
|
153 | QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), | |
153 | QueryColumn.new(:relations, :caption => :label_related_issues) |
|
154 | QueryColumn.new(:relations, :caption => :label_related_issues) | |
154 | ] |
|
155 | ] | |
155 | cattr_reader :available_columns |
|
156 | cattr_reader :available_columns | |
156 |
|
157 | |||
157 | scope :visible, lambda {|*args| |
|
158 | scope :visible, lambda {|*args| | |
158 | user = args.shift || User.current |
|
159 | user = args.shift || User.current | |
159 | base = Project.allowed_to_condition(user, :view_issues, *args) |
|
160 | base = Project.allowed_to_condition(user, :view_issues, *args) | |
160 | user_id = user.logged? ? user.id : 0 |
|
161 | user_id = user.logged? ? user.id : 0 | |
161 | { |
|
162 | { | |
162 | :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id], |
|
163 | :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id], | |
163 | :include => :project |
|
164 | :include => :project | |
164 | } |
|
165 | } | |
165 | } |
|
166 | } | |
166 |
|
167 | |||
167 | def initialize(attributes=nil, *args) |
|
168 | def initialize(attributes=nil, *args) | |
168 | super attributes |
|
169 | super attributes | |
169 | self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } |
|
170 | self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } | |
170 | @is_for_all = project.nil? |
|
171 | @is_for_all = project.nil? | |
171 | end |
|
172 | end | |
172 |
|
173 | |||
173 | def validate_query_filters |
|
174 | def validate_query_filters | |
174 | filters.each_key do |field| |
|
175 | filters.each_key do |field| | |
175 | if values_for(field) |
|
176 | if values_for(field) | |
176 | case type_for(field) |
|
177 | case type_for(field) | |
177 | when :integer |
|
178 | when :integer | |
178 | add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) } |
|
179 | add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) } | |
179 | when :float |
|
180 | when :float | |
180 | add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) } |
|
181 | add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) } | |
181 | when :date, :date_past |
|
182 | when :date, :date_past | |
182 | case operator_for(field) |
|
183 | case operator_for(field) | |
183 | when "=", ">=", "<=", "><" |
|
184 | when "=", ">=", "<=", "><" | |
184 | 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?) } |
|
185 | 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?) } | |
185 | when ">t-", "<t-", "t-" |
|
186 | when ">t-", "<t-", "t-" | |
186 | add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) } |
|
187 | add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) } | |
187 | end |
|
188 | end | |
188 | end |
|
189 | end | |
189 | end |
|
190 | end | |
190 |
|
191 | |||
191 | add_filter_error(field, :blank) unless |
|
192 | add_filter_error(field, :blank) unless | |
192 | # filter requires one or more values |
|
193 | # filter requires one or more values | |
193 | (values_for(field) and !values_for(field).first.blank?) or |
|
194 | (values_for(field) and !values_for(field).first.blank?) or | |
194 | # filter doesn't require any value |
|
195 | # filter doesn't require any value | |
195 | ["o", "c", "!*", "*", "t", "w"].include? operator_for(field) |
|
196 | ["o", "c", "!*", "*", "t", "w"].include? operator_for(field) | |
196 | end if filters |
|
197 | end if filters | |
197 | end |
|
198 | end | |
198 |
|
199 | |||
199 | def add_filter_error(field, message) |
|
200 | def add_filter_error(field, message) | |
200 | m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages') |
|
201 | m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages') | |
201 | errors.add(:base, m) |
|
202 | errors.add(:base, m) | |
202 | end |
|
203 | end | |
203 |
|
204 | |||
204 | # Returns true if the query is visible to +user+ or the current user. |
|
205 | # Returns true if the query is visible to +user+ or the current user. | |
205 | def visible?(user=User.current) |
|
206 | def visible?(user=User.current) | |
206 | (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id) |
|
207 | (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id) | |
207 | end |
|
208 | end | |
208 |
|
209 | |||
209 | def editable_by?(user) |
|
210 | def editable_by?(user) | |
210 | return false unless user |
|
211 | return false unless user | |
211 | # Admin can edit them all and regular users can edit their private queries |
|
212 | # Admin can edit them all and regular users can edit their private queries | |
212 | return true if user.admin? || (!is_public && self.user_id == user.id) |
|
213 | return true if user.admin? || (!is_public && self.user_id == user.id) | |
213 | # Members can not edit public queries that are for all project (only admin is allowed to) |
|
214 | # Members can not edit public queries that are for all project (only admin is allowed to) | |
214 | is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project) |
|
215 | is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project) | |
215 | end |
|
216 | end | |
216 |
|
217 | |||
217 | def trackers |
|
218 | def trackers | |
218 | @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers |
|
219 | @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers | |
219 | end |
|
220 | end | |
220 |
|
221 | |||
221 | # Returns a hash of localized labels for all filter operators |
|
222 | # Returns a hash of localized labels for all filter operators | |
222 | def self.operators_labels |
|
223 | def self.operators_labels | |
223 | operators.inject({}) {|h, operator| h[operator.first] = l(operator.last); h} |
|
224 | operators.inject({}) {|h, operator| h[operator.first] = l(operator.last); h} | |
224 | end |
|
225 | end | |
225 |
|
226 | |||
226 | def available_filters |
|
227 | def available_filters | |
227 | return @available_filters if @available_filters |
|
228 | return @available_filters if @available_filters | |
228 | @available_filters = { |
|
229 | @available_filters = { | |
229 | "status_id" => { |
|
230 | "status_id" => { | |
230 | :type => :list_status, :order => 0, |
|
231 | :type => :list_status, :order => 0, | |
231 | :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } |
|
232 | :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } | |
232 | }, |
|
233 | }, | |
233 | "tracker_id" => { |
|
234 | "tracker_id" => { | |
234 | :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } |
|
235 | :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } | |
235 | }, |
|
236 | }, | |
236 | "priority_id" => { |
|
237 | "priority_id" => { | |
237 | :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } |
|
238 | :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } | |
238 | }, |
|
239 | }, | |
239 | "subject" => { :type => :text, :order => 8 }, |
|
240 | "subject" => { :type => :text, :order => 8 }, | |
240 | "created_on" => { :type => :date_past, :order => 9 }, |
|
241 | "created_on" => { :type => :date_past, :order => 9 }, | |
241 | "updated_on" => { :type => :date_past, :order => 10 }, |
|
242 | "updated_on" => { :type => :date_past, :order => 10 }, | |
242 | "start_date" => { :type => :date, :order => 11 }, |
|
243 | "start_date" => { :type => :date, :order => 11 }, | |
243 | "due_date" => { :type => :date, :order => 12 }, |
|
244 | "due_date" => { :type => :date, :order => 12 }, | |
244 | "estimated_hours" => { :type => :float, :order => 13 }, |
|
245 | "estimated_hours" => { :type => :float, :order => 13 }, | |
245 | "done_ratio" => { :type => :integer, :order => 14 } |
|
246 | "done_ratio" => { :type => :integer, :order => 14 } | |
246 | } |
|
247 | } | |
247 | IssueRelation::TYPES.each do |relation_type, options| |
|
248 | IssueRelation::TYPES.each do |relation_type, options| | |
248 | @available_filters[relation_type] = { |
|
249 | @available_filters[relation_type] = { | |
249 | :type => :relation, :order => @available_filters.size + 100, |
|
250 | :type => :relation, :order => @available_filters.size + 100, | |
250 | :label => options[:name] |
|
251 | :label => options[:name] | |
251 | } |
|
252 | } | |
252 | end |
|
253 | end | |
253 | principals = [] |
|
254 | principals = [] | |
254 | if project |
|
255 | if project | |
255 | principals += project.principals.sort |
|
256 | principals += project.principals.sort | |
256 | unless project.leaf? |
|
257 | unless project.leaf? | |
257 | subprojects = project.descendants.visible.all |
|
258 | subprojects = project.descendants.visible.all | |
258 | if subprojects.any? |
|
259 | if subprojects.any? | |
259 | @available_filters["subproject_id"] = { |
|
260 | @available_filters["subproject_id"] = { | |
260 | :type => :list_subprojects, :order => 13, |
|
261 | :type => :list_subprojects, :order => 13, | |
261 | :values => subprojects.collect{|s| [s.name, s.id.to_s] } |
|
262 | :values => subprojects.collect{|s| [s.name, s.id.to_s] } | |
262 | } |
|
263 | } | |
263 | principals += Principal.member_of(subprojects) |
|
264 | principals += Principal.member_of(subprojects) | |
264 | end |
|
265 | end | |
265 | end |
|
266 | end | |
266 | else |
|
267 | else | |
267 | if all_projects.any? |
|
268 | if all_projects.any? | |
268 | # members of visible projects |
|
269 | # members of visible projects | |
269 | principals += Principal.member_of(all_projects) |
|
270 | principals += Principal.member_of(all_projects) | |
270 | # project filter |
|
271 | # project filter | |
271 | project_values = [] |
|
272 | project_values = [] | |
272 | if User.current.logged? && User.current.memberships.any? |
|
273 | if User.current.logged? && User.current.memberships.any? | |
273 | project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"] |
|
274 | project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"] | |
274 | end |
|
275 | end | |
275 | project_values += all_projects_values |
|
276 | project_values += all_projects_values | |
276 | @available_filters["project_id"] = { |
|
277 | @available_filters["project_id"] = { | |
277 | :type => :list, :order => 1, :values => project_values |
|
278 | :type => :list, :order => 1, :values => project_values | |
278 | } unless project_values.empty? |
|
279 | } unless project_values.empty? | |
279 | end |
|
280 | end | |
280 | end |
|
281 | end | |
281 | principals.uniq! |
|
282 | principals.uniq! | |
282 | principals.sort! |
|
283 | principals.sort! | |
283 | users = principals.select {|p| p.is_a?(User)} |
|
284 | users = principals.select {|p| p.is_a?(User)} | |
284 |
|
285 | |||
285 | assigned_to_values = [] |
|
286 | assigned_to_values = [] | |
286 | assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? |
|
287 | assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? | |
287 | assigned_to_values += (Setting.issue_group_assignment? ? |
|
288 | assigned_to_values += (Setting.issue_group_assignment? ? | |
288 | principals : users).collect{|s| [s.name, s.id.to_s] } |
|
289 | principals : users).collect{|s| [s.name, s.id.to_s] } | |
289 | @available_filters["assigned_to_id"] = { |
|
290 | @available_filters["assigned_to_id"] = { | |
290 | :type => :list_optional, :order => 4, :values => assigned_to_values |
|
291 | :type => :list_optional, :order => 4, :values => assigned_to_values | |
291 | } unless assigned_to_values.empty? |
|
292 | } unless assigned_to_values.empty? | |
292 |
|
293 | |||
293 | author_values = [] |
|
294 | author_values = [] | |
294 | author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? |
|
295 | author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? | |
295 | author_values += users.collect{|s| [s.name, s.id.to_s] } |
|
296 | author_values += users.collect{|s| [s.name, s.id.to_s] } | |
296 | @available_filters["author_id"] = { |
|
297 | @available_filters["author_id"] = { | |
297 | :type => :list, :order => 5, :values => author_values |
|
298 | :type => :list, :order => 5, :values => author_values | |
298 | } unless author_values.empty? |
|
299 | } unless author_values.empty? | |
299 |
|
300 | |||
300 | group_values = Group.all.collect {|g| [g.name, g.id.to_s] } |
|
301 | group_values = Group.all.collect {|g| [g.name, g.id.to_s] } | |
301 | @available_filters["member_of_group"] = { |
|
302 | @available_filters["member_of_group"] = { | |
302 | :type => :list_optional, :order => 6, :values => group_values |
|
303 | :type => :list_optional, :order => 6, :values => group_values | |
303 | } unless group_values.empty? |
|
304 | } unless group_values.empty? | |
304 |
|
305 | |||
305 | role_values = Role.givable.collect {|r| [r.name, r.id.to_s] } |
|
306 | role_values = Role.givable.collect {|r| [r.name, r.id.to_s] } | |
306 | @available_filters["assigned_to_role"] = { |
|
307 | @available_filters["assigned_to_role"] = { | |
307 | :type => :list_optional, :order => 7, :values => role_values |
|
308 | :type => :list_optional, :order => 7, :values => role_values | |
308 | } unless role_values.empty? |
|
309 | } unless role_values.empty? | |
309 |
|
310 | |||
310 | if User.current.logged? |
|
311 | if User.current.logged? | |
311 | @available_filters["watcher_id"] = { |
|
312 | @available_filters["watcher_id"] = { | |
312 | :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] |
|
313 | :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] | |
313 | } |
|
314 | } | |
314 | end |
|
315 | end | |
315 |
|
316 | |||
316 | if project |
|
317 | if project | |
317 | # project specific filters |
|
318 | # project specific filters | |
318 | categories = project.issue_categories.all |
|
319 | categories = project.issue_categories.all | |
319 | unless categories.empty? |
|
320 | unless categories.empty? | |
320 | @available_filters["category_id"] = { |
|
321 | @available_filters["category_id"] = { | |
321 | :type => :list_optional, :order => 6, |
|
322 | :type => :list_optional, :order => 6, | |
322 | :values => categories.collect{|s| [s.name, s.id.to_s] } |
|
323 | :values => categories.collect{|s| [s.name, s.id.to_s] } | |
323 | } |
|
324 | } | |
324 | end |
|
325 | end | |
325 | versions = project.shared_versions.all |
|
326 | versions = project.shared_versions.all | |
326 | unless versions.empty? |
|
327 | unless versions.empty? | |
327 | @available_filters["fixed_version_id"] = { |
|
328 | @available_filters["fixed_version_id"] = { | |
328 | :type => :list_optional, :order => 7, |
|
329 | :type => :list_optional, :order => 7, | |
329 | :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } |
|
330 | :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } | |
330 | } |
|
331 | } | |
331 | end |
|
332 | end | |
332 | add_custom_fields_filters(project.all_issue_custom_fields) |
|
333 | add_custom_fields_filters(project.all_issue_custom_fields) | |
333 | else |
|
334 | else | |
334 | # global filters for cross project issue list |
|
335 | # global filters for cross project issue list | |
335 | system_shared_versions = Version.visible.find_all_by_sharing('system') |
|
336 | system_shared_versions = Version.visible.find_all_by_sharing('system') | |
336 | unless system_shared_versions.empty? |
|
337 | unless system_shared_versions.empty? | |
337 | @available_filters["fixed_version_id"] = { |
|
338 | @available_filters["fixed_version_id"] = { | |
338 | :type => :list_optional, :order => 7, |
|
339 | :type => :list_optional, :order => 7, | |
339 | :values => system_shared_versions.sort.collect{|s| |
|
340 | :values => system_shared_versions.sort.collect{|s| | |
340 | ["#{s.project.name} - #{s.name}", s.id.to_s] |
|
341 | ["#{s.project.name} - #{s.name}", s.id.to_s] | |
341 | } |
|
342 | } | |
342 | } |
|
343 | } | |
343 | end |
|
344 | end | |
344 | add_custom_fields_filters( |
|
345 | add_custom_fields_filters( | |
345 | IssueCustomField.find(:all, |
|
346 | IssueCustomField.find(:all, | |
346 | :conditions => { |
|
347 | :conditions => { | |
347 | :is_filter => true, |
|
348 | :is_filter => true, | |
348 | :is_for_all => true |
|
349 | :is_for_all => true | |
349 | })) |
|
350 | })) | |
350 | end |
|
351 | end | |
351 | add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version |
|
352 | add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version | |
352 | if User.current.allowed_to?(:set_issues_private, nil, :global => true) || |
|
353 | if User.current.allowed_to?(:set_issues_private, nil, :global => true) || | |
353 | User.current.allowed_to?(:set_own_issues_private, nil, :global => true) |
|
354 | User.current.allowed_to?(:set_own_issues_private, nil, :global => true) | |
354 | @available_filters["is_private"] = { |
|
355 | @available_filters["is_private"] = { | |
355 | :type => :list, :order => 16, |
|
356 | :type => :list, :order => 16, | |
356 | :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] |
|
357 | :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] | |
357 | } |
|
358 | } | |
358 | end |
|
359 | end | |
359 | Tracker.disabled_core_fields(trackers).each {|field| |
|
360 | Tracker.disabled_core_fields(trackers).each {|field| | |
360 | @available_filters.delete field |
|
361 | @available_filters.delete field | |
361 | } |
|
362 | } | |
362 | @available_filters.each do |field, options| |
|
363 | @available_filters.each do |field, options| | |
363 | options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, '')) |
|
364 | options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, '')) | |
364 | end |
|
365 | end | |
365 | @available_filters |
|
366 | @available_filters | |
366 | end |
|
367 | end | |
367 |
|
368 | |||
368 | # Returns a representation of the available filters for JSON serialization |
|
369 | # Returns a representation of the available filters for JSON serialization | |
369 | def available_filters_as_json |
|
370 | def available_filters_as_json | |
370 | json = {} |
|
371 | json = {} | |
371 | available_filters.each do |field, options| |
|
372 | available_filters.each do |field, options| | |
372 | json[field] = options.slice(:type, :name, :values).stringify_keys |
|
373 | json[field] = options.slice(:type, :name, :values).stringify_keys | |
373 | end |
|
374 | end | |
374 | json |
|
375 | json | |
375 | end |
|
376 | end | |
376 |
|
377 | |||
377 | def all_projects |
|
378 | def all_projects | |
378 | @all_projects ||= Project.visible.all |
|
379 | @all_projects ||= Project.visible.all | |
379 | end |
|
380 | end | |
380 |
|
381 | |||
381 | def all_projects_values |
|
382 | def all_projects_values | |
382 | return @all_projects_values if @all_projects_values |
|
383 | return @all_projects_values if @all_projects_values | |
383 |
|
384 | |||
384 | values = [] |
|
385 | values = [] | |
385 | Project.project_tree(all_projects) do |p, level| |
|
386 | Project.project_tree(all_projects) do |p, level| | |
386 | prefix = (level > 0 ? ('--' * level + ' ') : '') |
|
387 | prefix = (level > 0 ? ('--' * level + ' ') : '') | |
387 | values << ["#{prefix}#{p.name}", p.id.to_s] |
|
388 | values << ["#{prefix}#{p.name}", p.id.to_s] | |
388 | end |
|
389 | end | |
389 | @all_projects_values = values |
|
390 | @all_projects_values = values | |
390 | end |
|
391 | end | |
391 |
|
392 | |||
392 | def add_filter(field, operator, values) |
|
393 | def add_filter(field, operator, values) | |
393 | # values must be an array |
|
394 | # values must be an array | |
394 | return unless values.nil? || values.is_a?(Array) |
|
395 | return unless values.nil? || values.is_a?(Array) | |
395 | # check if field is defined as an available filter |
|
396 | # check if field is defined as an available filter | |
396 | if available_filters.has_key? field |
|
397 | if available_filters.has_key? field | |
397 | filter_options = available_filters[field] |
|
398 | filter_options = available_filters[field] | |
398 | # check if operator is allowed for that filter |
|
399 | # check if operator is allowed for that filter | |
399 | #if @@operators_by_filter_type[filter_options[:type]].include? operator |
|
400 | #if @@operators_by_filter_type[filter_options[:type]].include? operator | |
400 | # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]}) |
|
401 | # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]}) | |
401 | # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator |
|
402 | # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator | |
402 | #end |
|
403 | #end | |
403 | filters[field] = {:operator => operator, :values => (values || [''])} |
|
404 | filters[field] = {:operator => operator, :values => (values || [''])} | |
404 | end |
|
405 | end | |
405 | end |
|
406 | end | |
406 |
|
407 | |||
407 | def add_short_filter(field, expression) |
|
408 | def add_short_filter(field, expression) | |
408 | return unless expression && available_filters.has_key?(field) |
|
409 | return unless expression && available_filters.has_key?(field) | |
409 | field_type = available_filters[field][:type] |
|
410 | field_type = available_filters[field][:type] | |
410 | @@operators_by_filter_type[field_type].sort.reverse.detect do |operator| |
|
411 | @@operators_by_filter_type[field_type].sort.reverse.detect do |operator| | |
411 | next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/ |
|
412 | next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/ | |
412 | add_filter field, operator, $1.present? ? $1.split('|') : [''] |
|
413 | add_filter field, operator, $1.present? ? $1.split('|') : [''] | |
413 | end || add_filter(field, '=', expression.split('|')) |
|
414 | end || add_filter(field, '=', expression.split('|')) | |
414 | end |
|
415 | end | |
415 |
|
416 | |||
416 | # Add multiple filters using +add_filter+ |
|
417 | # Add multiple filters using +add_filter+ | |
417 | def add_filters(fields, operators, values) |
|
418 | def add_filters(fields, operators, values) | |
418 | if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash)) |
|
419 | if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash)) | |
419 | fields.each do |field| |
|
420 | fields.each do |field| | |
420 | add_filter(field, operators[field], values && values[field]) |
|
421 | add_filter(field, operators[field], values && values[field]) | |
421 | end |
|
422 | end | |
422 | end |
|
423 | end | |
423 | end |
|
424 | end | |
424 |
|
425 | |||
425 | def has_filter?(field) |
|
426 | def has_filter?(field) | |
426 | filters and filters[field] |
|
427 | filters and filters[field] | |
427 | end |
|
428 | end | |
428 |
|
429 | |||
429 | def type_for(field) |
|
430 | def type_for(field) | |
430 | available_filters[field][:type] if available_filters.has_key?(field) |
|
431 | available_filters[field][:type] if available_filters.has_key?(field) | |
431 | end |
|
432 | end | |
432 |
|
433 | |||
433 | def operator_for(field) |
|
434 | def operator_for(field) | |
434 | has_filter?(field) ? filters[field][:operator] : nil |
|
435 | has_filter?(field) ? filters[field][:operator] : nil | |
435 | end |
|
436 | end | |
436 |
|
437 | |||
437 | def values_for(field) |
|
438 | def values_for(field) | |
438 | has_filter?(field) ? filters[field][:values] : nil |
|
439 | has_filter?(field) ? filters[field][:values] : nil | |
439 | end |
|
440 | end | |
440 |
|
441 | |||
441 | def value_for(field, index=0) |
|
442 | def value_for(field, index=0) | |
442 | (values_for(field) || [])[index] |
|
443 | (values_for(field) || [])[index] | |
443 | end |
|
444 | end | |
444 |
|
445 | |||
445 | def label_for(field) |
|
446 | def label_for(field) | |
446 | label = available_filters[field][:name] if available_filters.has_key?(field) |
|
447 | label = available_filters[field][:name] if available_filters.has_key?(field) | |
447 | label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field) |
|
448 | label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field) | |
448 | end |
|
449 | end | |
449 |
|
450 | |||
450 | def available_columns |
|
451 | def available_columns | |
451 | return @available_columns if @available_columns |
|
452 | return @available_columns if @available_columns | |
452 | @available_columns = ::Query.available_columns.dup |
|
453 | @available_columns = ::Query.available_columns.dup | |
453 | @available_columns += (project ? |
|
454 | @available_columns += (project ? | |
454 | project.all_issue_custom_fields : |
|
455 | project.all_issue_custom_fields : | |
455 | IssueCustomField.find(:all) |
|
456 | IssueCustomField.find(:all) | |
456 | ).collect {|cf| QueryCustomFieldColumn.new(cf) } |
|
457 | ).collect {|cf| QueryCustomFieldColumn.new(cf) } | |
457 |
|
458 | |||
458 | if User.current.allowed_to?(:view_time_entries, project, :global => true) |
|
459 | if User.current.allowed_to?(:view_time_entries, project, :global => true) | |
459 | index = nil |
|
460 | index = nil | |
460 | @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours} |
|
461 | @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours} | |
461 | index = (index ? index + 1 : -1) |
|
462 | index = (index ? index + 1 : -1) | |
462 | # insert the column after estimated_hours or at the end |
|
463 | # insert the column after estimated_hours or at the end | |
463 | @available_columns.insert index, QueryColumn.new(:spent_hours, |
|
464 | @available_columns.insert index, QueryColumn.new(:spent_hours, | |
464 | :sortable => "(SELECT COALESCE(SUM(hours), 0) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id)", |
|
465 | :sortable => "(SELECT COALESCE(SUM(hours), 0) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id)", | |
465 | :default_order => 'desc', |
|
466 | :default_order => 'desc', | |
466 | :caption => :label_spent_time |
|
467 | :caption => :label_spent_time | |
467 | ) |
|
468 | ) | |
468 | end |
|
469 | end | |
469 |
|
470 | |||
470 | if User.current.allowed_to?(:set_issues_private, nil, :global => true) || |
|
471 | if User.current.allowed_to?(:set_issues_private, nil, :global => true) || | |
471 | User.current.allowed_to?(:set_own_issues_private, nil, :global => true) |
|
472 | User.current.allowed_to?(:set_own_issues_private, nil, :global => true) | |
472 | @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private") |
|
473 | @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private") | |
473 | end |
|
474 | end | |
474 |
|
475 | |||
475 | disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')} |
|
476 | disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')} | |
476 | @available_columns.reject! {|column| |
|
477 | @available_columns.reject! {|column| | |
477 | disabled_fields.include?(column.name.to_s) |
|
478 | disabled_fields.include?(column.name.to_s) | |
478 | } |
|
479 | } | |
479 |
|
480 | |||
480 | @available_columns |
|
481 | @available_columns | |
481 | end |
|
482 | end | |
482 |
|
483 | |||
483 | def self.available_columns=(v) |
|
484 | def self.available_columns=(v) | |
484 | self.available_columns = (v) |
|
485 | self.available_columns = (v) | |
485 | end |
|
486 | end | |
486 |
|
487 | |||
487 | def self.add_available_column(column) |
|
488 | def self.add_available_column(column) | |
488 | self.available_columns << (column) if column.is_a?(QueryColumn) |
|
489 | self.available_columns << (column) if column.is_a?(QueryColumn) | |
489 | end |
|
490 | end | |
490 |
|
491 | |||
491 | # Returns an array of columns that can be used to group the results |
|
492 | # Returns an array of columns that can be used to group the results | |
492 | def groupable_columns |
|
493 | def groupable_columns | |
493 | available_columns.select {|c| c.groupable} |
|
494 | available_columns.select {|c| c.groupable} | |
494 | end |
|
495 | end | |
495 |
|
496 | |||
496 | # Returns a Hash of columns and the key for sorting |
|
497 | # Returns a Hash of columns and the key for sorting | |
497 | def sortable_columns |
|
498 | def sortable_columns | |
498 | {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column| |
|
499 | {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column| | |
499 | h[column.name.to_s] = column.sortable |
|
500 | h[column.name.to_s] = column.sortable | |
500 | h |
|
501 | h | |
501 | }) |
|
502 | }) | |
502 | end |
|
503 | end | |
503 |
|
504 | |||
504 | def columns |
|
505 | def columns | |
505 | # preserve the column_names order |
|
506 | # preserve the column_names order | |
506 | (has_default_columns? ? default_columns_names : column_names).collect do |name| |
|
507 | (has_default_columns? ? default_columns_names : column_names).collect do |name| | |
507 | available_columns.find { |col| col.name == name } |
|
508 | available_columns.find { |col| col.name == name } | |
508 | end.compact |
|
509 | end.compact | |
509 | end |
|
510 | end | |
510 |
|
511 | |||
511 | def default_columns_names |
|
512 | def default_columns_names | |
512 | @default_columns_names ||= begin |
|
513 | @default_columns_names ||= begin | |
513 | default_columns = Setting.issue_list_default_columns.map(&:to_sym) |
|
514 | default_columns = Setting.issue_list_default_columns.map(&:to_sym) | |
514 |
|
515 | |||
515 | project.present? ? default_columns : [:project] | default_columns |
|
516 | project.present? ? default_columns : [:project] | default_columns | |
516 | end |
|
517 | end | |
517 | end |
|
518 | end | |
518 |
|
519 | |||
519 | def column_names=(names) |
|
520 | def column_names=(names) | |
520 | if names |
|
521 | if names | |
521 | names = names.select {|n| n.is_a?(Symbol) || !n.blank? } |
|
522 | names = names.select {|n| n.is_a?(Symbol) || !n.blank? } | |
522 | names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } |
|
523 | names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } | |
523 | # Set column_names to nil if default columns |
|
524 | # Set column_names to nil if default columns | |
524 | if names == default_columns_names |
|
525 | if names == default_columns_names | |
525 | names = nil |
|
526 | names = nil | |
526 | end |
|
527 | end | |
527 | end |
|
528 | end | |
528 | write_attribute(:column_names, names) |
|
529 | write_attribute(:column_names, names) | |
529 | end |
|
530 | end | |
530 |
|
531 | |||
531 | def has_column?(column) |
|
532 | def has_column?(column) | |
532 | column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column) |
|
533 | column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column) | |
533 | end |
|
534 | end | |
534 |
|
535 | |||
535 | def has_default_columns? |
|
536 | def has_default_columns? | |
536 | column_names.nil? || column_names.empty? |
|
537 | column_names.nil? || column_names.empty? | |
537 | end |
|
538 | end | |
538 |
|
539 | |||
539 | def sort_criteria=(arg) |
|
540 | def sort_criteria=(arg) | |
540 | c = [] |
|
541 | c = [] | |
541 | if arg.is_a?(Hash) |
|
542 | if arg.is_a?(Hash) | |
542 | arg = arg.keys.sort.collect {|k| arg[k]} |
|
543 | arg = arg.keys.sort.collect {|k| arg[k]} | |
543 | end |
|
544 | end | |
544 | c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']} |
|
545 | c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']} | |
545 | write_attribute(:sort_criteria, c) |
|
546 | write_attribute(:sort_criteria, c) | |
546 | end |
|
547 | end | |
547 |
|
548 | |||
548 | def sort_criteria |
|
549 | def sort_criteria | |
549 | read_attribute(:sort_criteria) || [] |
|
550 | read_attribute(:sort_criteria) || [] | |
550 | end |
|
551 | end | |
551 |
|
552 | |||
552 | def sort_criteria_key(arg) |
|
553 | def sort_criteria_key(arg) | |
553 | sort_criteria && sort_criteria[arg] && sort_criteria[arg].first |
|
554 | sort_criteria && sort_criteria[arg] && sort_criteria[arg].first | |
554 | end |
|
555 | end | |
555 |
|
556 | |||
556 | def sort_criteria_order(arg) |
|
557 | def sort_criteria_order(arg) | |
557 | sort_criteria && sort_criteria[arg] && sort_criteria[arg].last |
|
558 | sort_criteria && sort_criteria[arg] && sort_criteria[arg].last | |
558 | end |
|
559 | end | |
559 |
|
560 | |||
560 | # Returns the SQL sort order that should be prepended for grouping |
|
561 | # Returns the SQL sort order that should be prepended for grouping | |
561 | def group_by_sort_order |
|
562 | def group_by_sort_order | |
562 | if grouped? && (column = group_by_column) |
|
563 | if grouped? && (column = group_by_column) | |
563 | column.sortable.is_a?(Array) ? |
|
564 | column.sortable.is_a?(Array) ? | |
564 | column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') : |
|
565 | column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') : | |
565 | "#{column.sortable} #{column.default_order}" |
|
566 | "#{column.sortable} #{column.default_order}" | |
566 | end |
|
567 | end | |
567 | end |
|
568 | end | |
568 |
|
569 | |||
569 | # Returns true if the query is a grouped query |
|
570 | # Returns true if the query is a grouped query | |
570 | def grouped? |
|
571 | def grouped? | |
571 | !group_by_column.nil? |
|
572 | !group_by_column.nil? | |
572 | end |
|
573 | end | |
573 |
|
574 | |||
574 | def group_by_column |
|
575 | def group_by_column | |
575 | groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by} |
|
576 | groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by} | |
576 | end |
|
577 | end | |
577 |
|
578 | |||
578 | def group_by_statement |
|
579 | def group_by_statement | |
579 | group_by_column.try(:groupable) |
|
580 | group_by_column.try(:groupable) | |
580 | end |
|
581 | end | |
581 |
|
582 | |||
582 | def project_statement |
|
583 | def project_statement | |
583 | project_clauses = [] |
|
584 | project_clauses = [] | |
584 | if project && !project.descendants.active.empty? |
|
585 | if project && !project.descendants.active.empty? | |
585 | ids = [project.id] |
|
586 | ids = [project.id] | |
586 | if has_filter?("subproject_id") |
|
587 | if has_filter?("subproject_id") | |
587 | case operator_for("subproject_id") |
|
588 | case operator_for("subproject_id") | |
588 | when '=' |
|
589 | when '=' | |
589 | # include the selected subprojects |
|
590 | # include the selected subprojects | |
590 | ids += values_for("subproject_id").each(&:to_i) |
|
591 | ids += values_for("subproject_id").each(&:to_i) | |
591 | when '!*' |
|
592 | when '!*' | |
592 | # main project only |
|
593 | # main project only | |
593 | else |
|
594 | else | |
594 | # all subprojects |
|
595 | # all subprojects | |
595 | ids += project.descendants.collect(&:id) |
|
596 | ids += project.descendants.collect(&:id) | |
596 | end |
|
597 | end | |
597 | elsif Setting.display_subprojects_issues? |
|
598 | elsif Setting.display_subprojects_issues? | |
598 | ids += project.descendants.collect(&:id) |
|
599 | ids += project.descendants.collect(&:id) | |
599 | end |
|
600 | end | |
600 | project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',') |
|
601 | project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',') | |
601 | elsif project |
|
602 | elsif project | |
602 | project_clauses << "#{Project.table_name}.id = %d" % project.id |
|
603 | project_clauses << "#{Project.table_name}.id = %d" % project.id | |
603 | end |
|
604 | end | |
604 | project_clauses.any? ? project_clauses.join(' AND ') : nil |
|
605 | project_clauses.any? ? project_clauses.join(' AND ') : nil | |
605 | end |
|
606 | end | |
606 |
|
607 | |||
607 | def statement |
|
608 | def statement | |
608 | # filters clauses |
|
609 | # filters clauses | |
609 | filters_clauses = [] |
|
610 | filters_clauses = [] | |
610 | filters.each_key do |field| |
|
611 | filters.each_key do |field| | |
611 | next if field == "subproject_id" |
|
612 | next if field == "subproject_id" | |
612 | v = values_for(field).clone |
|
613 | v = values_for(field).clone | |
613 | next unless v and !v.empty? |
|
614 | next unless v and !v.empty? | |
614 | operator = operator_for(field) |
|
615 | operator = operator_for(field) | |
615 |
|
616 | |||
616 | # "me" value subsitution |
|
617 | # "me" value subsitution | |
617 | if %w(assigned_to_id author_id watcher_id).include?(field) |
|
618 | if %w(assigned_to_id author_id watcher_id).include?(field) | |
618 | if v.delete("me") |
|
619 | if v.delete("me") | |
619 | if User.current.logged? |
|
620 | if User.current.logged? | |
620 | v.push(User.current.id.to_s) |
|
621 | v.push(User.current.id.to_s) | |
621 | v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id' |
|
622 | v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id' | |
622 | else |
|
623 | else | |
623 | v.push("0") |
|
624 | v.push("0") | |
624 | end |
|
625 | end | |
625 | end |
|
626 | end | |
626 | end |
|
627 | end | |
627 |
|
628 | |||
628 | if field == 'project_id' |
|
629 | if field == 'project_id' | |
629 | if v.delete('mine') |
|
630 | if v.delete('mine') | |
630 | v += User.current.memberships.map(&:project_id).map(&:to_s) |
|
631 | v += User.current.memberships.map(&:project_id).map(&:to_s) | |
631 | end |
|
632 | end | |
632 | end |
|
633 | end | |
633 |
|
634 | |||
634 | if field =~ /cf_(\d+)$/ |
|
635 | if field =~ /cf_(\d+)$/ | |
635 | # custom field |
|
636 | # custom field | |
636 | filters_clauses << sql_for_custom_field(field, operator, v, $1) |
|
637 | filters_clauses << sql_for_custom_field(field, operator, v, $1) | |
637 | elsif respond_to?("sql_for_#{field}_field") |
|
638 | elsif respond_to?("sql_for_#{field}_field") | |
638 | # specific statement |
|
639 | # specific statement | |
639 | filters_clauses << send("sql_for_#{field}_field", field, operator, v) |
|
640 | filters_clauses << send("sql_for_#{field}_field", field, operator, v) | |
640 | else |
|
641 | else | |
641 | # regular field |
|
642 | # regular field | |
642 | filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')' |
|
643 | filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')' | |
643 | end |
|
644 | end | |
644 | end if filters and valid? |
|
645 | end if filters and valid? | |
645 |
|
646 | |||
646 | filters_clauses << project_statement |
|
647 | filters_clauses << project_statement | |
647 | filters_clauses.reject!(&:blank?) |
|
648 | filters_clauses.reject!(&:blank?) | |
648 |
|
649 | |||
649 | filters_clauses.any? ? filters_clauses.join(' AND ') : nil |
|
650 | filters_clauses.any? ? filters_clauses.join(' AND ') : nil | |
650 | end |
|
651 | end | |
651 |
|
652 | |||
652 | # Returns the issue count |
|
653 | # Returns the issue count | |
653 | def issue_count |
|
654 | def issue_count | |
654 | Issue.visible.count(:include => [:status, :project], :conditions => statement) |
|
655 | Issue.visible.count(:include => [:status, :project], :conditions => statement) | |
655 | rescue ::ActiveRecord::StatementInvalid => e |
|
656 | rescue ::ActiveRecord::StatementInvalid => e | |
656 | raise StatementInvalid.new(e.message) |
|
657 | raise StatementInvalid.new(e.message) | |
657 | end |
|
658 | end | |
658 |
|
659 | |||
659 | # Returns the issue count by group or nil if query is not grouped |
|
660 | # Returns the issue count by group or nil if query is not grouped | |
660 | def issue_count_by_group |
|
661 | def issue_count_by_group | |
661 | r = nil |
|
662 | r = nil | |
662 | if grouped? |
|
663 | if grouped? | |
663 | begin |
|
664 | begin | |
664 | # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value |
|
665 | # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value | |
665 | r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement) |
|
666 | r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement) | |
666 | rescue ActiveRecord::RecordNotFound |
|
667 | rescue ActiveRecord::RecordNotFound | |
667 | r = {nil => issue_count} |
|
668 | r = {nil => issue_count} | |
668 | end |
|
669 | end | |
669 | c = group_by_column |
|
670 | c = group_by_column | |
670 | if c.is_a?(QueryCustomFieldColumn) |
|
671 | if c.is_a?(QueryCustomFieldColumn) | |
671 | r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h} |
|
672 | r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h} | |
672 | end |
|
673 | end | |
673 | end |
|
674 | end | |
674 | r |
|
675 | r | |
675 | rescue ::ActiveRecord::StatementInvalid => e |
|
676 | rescue ::ActiveRecord::StatementInvalid => e | |
676 | raise StatementInvalid.new(e.message) |
|
677 | raise StatementInvalid.new(e.message) | |
677 | end |
|
678 | end | |
678 |
|
679 | |||
679 | # Returns the issues |
|
680 | # Returns the issues | |
680 | # Valid options are :order, :offset, :limit, :include, :conditions |
|
681 | # Valid options are :order, :offset, :limit, :include, :conditions | |
681 | def issues(options={}) |
|
682 | def issues(options={}) | |
682 | order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') |
|
683 | order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') | |
683 | order_option = nil if order_option.blank? |
|
684 | order_option = nil if order_option.blank? | |
684 |
|
685 | |||
685 | issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq, |
|
686 | issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq, | |
686 | :conditions => statement, |
|
687 | :conditions => statement, | |
687 | :order => order_option, |
|
688 | :order => order_option, | |
688 | :joins => joins_for_order_statement(order_option), |
|
689 | :joins => joins_for_order_statement(order_option), | |
689 | :limit => options[:limit], |
|
690 | :limit => options[:limit], | |
690 | :offset => options[:offset] |
|
691 | :offset => options[:offset] | |
691 |
|
692 | |||
692 | if has_column?(:spent_hours) |
|
693 | if has_column?(:spent_hours) | |
693 | Issue.load_visible_spent_hours(issues) |
|
694 | Issue.load_visible_spent_hours(issues) | |
694 | end |
|
695 | end | |
695 | if has_column?(:relations) |
|
696 | if has_column?(:relations) | |
696 | Issue.load_visible_relations(issues) |
|
697 | Issue.load_visible_relations(issues) | |
697 | end |
|
698 | end | |
698 | issues |
|
699 | issues | |
699 | rescue ::ActiveRecord::StatementInvalid => e |
|
700 | rescue ::ActiveRecord::StatementInvalid => e | |
700 | raise StatementInvalid.new(e.message) |
|
701 | raise StatementInvalid.new(e.message) | |
701 | end |
|
702 | end | |
702 |
|
703 | |||
703 | # Returns the issues ids |
|
704 | # Returns the issues ids | |
704 | def issue_ids(options={}) |
|
705 | def issue_ids(options={}) | |
705 | order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') |
|
706 | order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') | |
706 | order_option = nil if order_option.blank? |
|
707 | order_option = nil if order_option.blank? | |
707 |
|
708 | |||
708 | Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq, |
|
709 | Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq, | |
709 | :conditions => statement, |
|
710 | :conditions => statement, | |
710 | :order => order_option, |
|
711 | :order => order_option, | |
711 | :joins => joins_for_order_statement(order_option), |
|
712 | :joins => joins_for_order_statement(order_option), | |
712 | :limit => options[:limit], |
|
713 | :limit => options[:limit], | |
713 | :offset => options[:offset]).find_ids |
|
714 | :offset => options[:offset]).find_ids | |
714 | rescue ::ActiveRecord::StatementInvalid => e |
|
715 | rescue ::ActiveRecord::StatementInvalid => e | |
715 | raise StatementInvalid.new(e.message) |
|
716 | raise StatementInvalid.new(e.message) | |
716 | end |
|
717 | end | |
717 |
|
718 | |||
718 | # Returns the journals |
|
719 | # Returns the journals | |
719 | # Valid options are :order, :offset, :limit |
|
720 | # Valid options are :order, :offset, :limit | |
720 | def journals(options={}) |
|
721 | def journals(options={}) | |
721 | Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], |
|
722 | Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], | |
722 | :conditions => statement, |
|
723 | :conditions => statement, | |
723 | :order => options[:order], |
|
724 | :order => options[:order], | |
724 | :limit => options[:limit], |
|
725 | :limit => options[:limit], | |
725 | :offset => options[:offset] |
|
726 | :offset => options[:offset] | |
726 | rescue ::ActiveRecord::StatementInvalid => e |
|
727 | rescue ::ActiveRecord::StatementInvalid => e | |
727 | raise StatementInvalid.new(e.message) |
|
728 | raise StatementInvalid.new(e.message) | |
728 | end |
|
729 | end | |
729 |
|
730 | |||
730 | # Returns the versions |
|
731 | # Returns the versions | |
731 | # Valid options are :conditions |
|
732 | # Valid options are :conditions | |
732 | def versions(options={}) |
|
733 | def versions(options={}) | |
733 | Version.visible.scoped(:conditions => options[:conditions]).find :all, :include => :project, :conditions => project_statement |
|
734 | Version.visible.scoped(:conditions => options[:conditions]).find :all, :include => :project, :conditions => project_statement | |
734 | rescue ::ActiveRecord::StatementInvalid => e |
|
735 | rescue ::ActiveRecord::StatementInvalid => e | |
735 | raise StatementInvalid.new(e.message) |
|
736 | raise StatementInvalid.new(e.message) | |
736 | end |
|
737 | end | |
737 |
|
738 | |||
738 | def sql_for_watcher_id_field(field, operator, value) |
|
739 | def sql_for_watcher_id_field(field, operator, value) | |
739 | db_table = Watcher.table_name |
|
740 | db_table = Watcher.table_name | |
740 | "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + |
|
741 | "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + | |
741 | sql_for_field(field, '=', value, db_table, 'user_id') + ')' |
|
742 | sql_for_field(field, '=', value, db_table, 'user_id') + ')' | |
742 | end |
|
743 | end | |
743 |
|
744 | |||
744 | def sql_for_member_of_group_field(field, operator, value) |
|
745 | def sql_for_member_of_group_field(field, operator, value) | |
745 | if operator == '*' # Any group |
|
746 | if operator == '*' # Any group | |
746 | groups = Group.all |
|
747 | groups = Group.all | |
747 | operator = '=' # Override the operator since we want to find by assigned_to |
|
748 | operator = '=' # Override the operator since we want to find by assigned_to | |
748 | elsif operator == "!*" |
|
749 | elsif operator == "!*" | |
749 | groups = Group.all |
|
750 | groups = Group.all | |
750 | operator = '!' # Override the operator since we want to find by assigned_to |
|
751 | operator = '!' # Override the operator since we want to find by assigned_to | |
751 | else |
|
752 | else | |
752 | groups = Group.find_all_by_id(value) |
|
753 | groups = Group.find_all_by_id(value) | |
753 | end |
|
754 | end | |
754 | groups ||= [] |
|
755 | groups ||= [] | |
755 |
|
756 | |||
756 | members_of_groups = groups.inject([]) {|user_ids, group| |
|
757 | members_of_groups = groups.inject([]) {|user_ids, group| | |
757 | if group && group.user_ids.present? |
|
758 | if group && group.user_ids.present? | |
758 | user_ids << group.user_ids |
|
759 | user_ids << group.user_ids | |
759 | end |
|
760 | end | |
760 | user_ids.flatten.uniq.compact |
|
761 | user_ids.flatten.uniq.compact | |
761 | }.sort.collect(&:to_s) |
|
762 | }.sort.collect(&:to_s) | |
762 |
|
763 | |||
763 | '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' |
|
764 | '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' | |
764 | end |
|
765 | end | |
765 |
|
766 | |||
766 | def sql_for_assigned_to_role_field(field, operator, value) |
|
767 | def sql_for_assigned_to_role_field(field, operator, value) | |
767 | case operator |
|
768 | case operator | |
768 | when "*", "!*" # Member / Not member |
|
769 | when "*", "!*" # Member / Not member | |
769 | sw = operator == "!*" ? 'NOT' : '' |
|
770 | sw = operator == "!*" ? 'NOT' : '' | |
770 | nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' |
|
771 | nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' | |
771 | "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" + |
|
772 | "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" + | |
772 | " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))" |
|
773 | " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))" | |
773 | when "=", "!" |
|
774 | when "=", "!" | |
774 | role_cond = value.any? ? |
|
775 | role_cond = value.any? ? | |
775 | "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" : |
|
776 | "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" : | |
776 | "1=0" |
|
777 | "1=0" | |
777 |
|
778 | |||
778 | sw = operator == "!" ? 'NOT' : '' |
|
779 | sw = operator == "!" ? 'NOT' : '' | |
779 | nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' |
|
780 | nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' | |
780 | "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" + |
|
781 | "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" + | |
781 | " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))" |
|
782 | " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))" | |
782 | end |
|
783 | end | |
783 | end |
|
784 | end | |
784 |
|
785 | |||
785 | def sql_for_is_private_field(field, operator, value) |
|
786 | def sql_for_is_private_field(field, operator, value) | |
786 | op = (operator == "=" ? 'IN' : 'NOT IN') |
|
787 | op = (operator == "=" ? 'IN' : 'NOT IN') | |
787 | va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',') |
|
788 | va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',') | |
788 |
|
789 | |||
789 | "#{Issue.table_name}.is_private #{op} (#{va})" |
|
790 | "#{Issue.table_name}.is_private #{op} (#{va})" | |
790 | end |
|
791 | end | |
791 |
|
792 | |||
792 | def sql_for_relations(field, operator, value, options={}) |
|
793 | def sql_for_relations(field, operator, value, options={}) | |
793 | relation_options = IssueRelation::TYPES[field] |
|
794 | relation_options = IssueRelation::TYPES[field] | |
794 | return relation_options unless relation_options |
|
795 | return relation_options unless relation_options | |
795 |
|
796 | |||
796 | relation_type = field |
|
797 | relation_type = field | |
797 | join_column, target_join_column = "issue_from_id", "issue_to_id" |
|
798 | join_column, target_join_column = "issue_from_id", "issue_to_id" | |
798 | if relation_options[:reverse] || options[:reverse] |
|
799 | if relation_options[:reverse] || options[:reverse] | |
799 | relation_type = relation_options[:reverse] || relation_type |
|
800 | relation_type = relation_options[:reverse] || relation_type | |
800 | join_column, target_join_column = target_join_column, join_column |
|
801 | join_column, target_join_column = target_join_column, join_column | |
801 | end |
|
802 | end | |
802 |
|
803 | |||
803 | sql = case operator |
|
804 | sql = case operator | |
804 | when "*", "!*" |
|
805 | when "*", "!*" | |
805 | op = (operator == "*" ? 'IN' : 'NOT IN') |
|
806 | op = (operator == "*" ? 'IN' : 'NOT IN') | |
806 | "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')" |
|
807 | "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')" | |
807 | when "=", "!" |
|
808 | when "=", "!" | |
808 | op = (operator == "=" ? 'IN' : 'NOT IN') |
|
809 | op = (operator == "=" ? 'IN' : 'NOT IN') | |
809 | "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" |
|
810 | "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" | |
810 | when "=p", "=!p" |
|
811 | when "=p", "=!p", "!p" | |
811 |
op = (operator == " |
|
812 | op = (operator == "!p" ? 'NOT IN' : 'IN') | |
812 | "#{Issue.table_name}.id IN (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{op} #{value.first.to_i})" |
|
813 | comp = (operator == "=!p" ? '<>' : '=') | |
|
814 | "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})" | |||
813 | end |
|
815 | end | |
814 |
|
816 | |||
815 | if relation_options[:sym] == field && !options[:reverse] |
|
817 | if relation_options[:sym] == field && !options[:reverse] | |
816 | sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)] |
|
818 | sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)] | |
817 | sqls.join(["!", "!*"].include?(operator) ? " AND " : " OR ") |
|
819 | sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ") | |
818 | else |
|
820 | else | |
819 | sql |
|
821 | sql | |
820 | end |
|
822 | end | |
821 | end |
|
823 | end | |
822 |
|
824 | |||
823 | IssueRelation::TYPES.keys.each do |relation_type| |
|
825 | IssueRelation::TYPES.keys.each do |relation_type| | |
824 | alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations |
|
826 | alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations | |
825 | end |
|
827 | end | |
826 |
|
828 | |||
827 | private |
|
829 | private | |
828 |
|
830 | |||
829 | def sql_for_custom_field(field, operator, value, custom_field_id) |
|
831 | def sql_for_custom_field(field, operator, value, custom_field_id) | |
830 | db_table = CustomValue.table_name |
|
832 | db_table = CustomValue.table_name | |
831 | db_field = 'value' |
|
833 | db_field = 'value' | |
832 | filter = @available_filters[field] |
|
834 | filter = @available_filters[field] | |
833 | return nil unless filter |
|
835 | return nil unless filter | |
834 | if filter[:format] == 'user' |
|
836 | if filter[:format] == 'user' | |
835 | if value.delete('me') |
|
837 | if value.delete('me') | |
836 | value.push User.current.id.to_s |
|
838 | value.push User.current.id.to_s | |
837 | end |
|
839 | end | |
838 | end |
|
840 | end | |
839 | not_in = nil |
|
841 | not_in = nil | |
840 | if operator == '!' |
|
842 | if operator == '!' | |
841 | # Makes ! operator work for custom fields with multiple values |
|
843 | # Makes ! operator work for custom fields with multiple values | |
842 | operator = '=' |
|
844 | operator = '=' | |
843 | not_in = 'NOT' |
|
845 | not_in = 'NOT' | |
844 | end |
|
846 | end | |
845 | customized_key = "id" |
|
847 | customized_key = "id" | |
846 | customized_class = Issue |
|
848 | customized_class = Issue | |
847 | if field =~ /^(.+)\.cf_/ |
|
849 | if field =~ /^(.+)\.cf_/ | |
848 | assoc = $1 |
|
850 | assoc = $1 | |
849 | customized_key = "#{assoc}_id" |
|
851 | customized_key = "#{assoc}_id" | |
850 | customized_class = Issue.reflect_on_association(assoc.to_sym).klass.base_class rescue nil |
|
852 | customized_class = Issue.reflect_on_association(assoc.to_sym).klass.base_class rescue nil | |
851 | raise "Unknown Issue association #{assoc}" unless customized_class |
|
853 | raise "Unknown Issue association #{assoc}" unless customized_class | |
852 | end |
|
854 | end | |
853 | "#{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 " + |
|
855 | "#{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 " + | |
854 | sql_for_field(field, operator, value, db_table, db_field, true) + ')' |
|
856 | sql_for_field(field, operator, value, db_table, db_field, true) + ')' | |
855 | end |
|
857 | end | |
856 |
|
858 | |||
857 | # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ |
|
859 | # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ | |
858 | def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false) |
|
860 | def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false) | |
859 | sql = '' |
|
861 | sql = '' | |
860 | case operator |
|
862 | case operator | |
861 | when "=" |
|
863 | when "=" | |
862 | if value.any? |
|
864 | if value.any? | |
863 | case type_for(field) |
|
865 | case type_for(field) | |
864 | when :date, :date_past |
|
866 | when :date, :date_past | |
865 | sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil)) |
|
867 | sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil)) | |
866 | when :integer |
|
868 | when :integer | |
867 | if is_custom_filter |
|
869 | if is_custom_filter | |
868 | sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) = #{value.first.to_i})" |
|
870 | sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) = #{value.first.to_i})" | |
869 | else |
|
871 | else | |
870 | sql = "#{db_table}.#{db_field} = #{value.first.to_i}" |
|
872 | sql = "#{db_table}.#{db_field} = #{value.first.to_i}" | |
871 | end |
|
873 | end | |
872 | when :float |
|
874 | when :float | |
873 | if is_custom_filter |
|
875 | if is_custom_filter | |
874 | 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})" |
|
876 | 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})" | |
875 | else |
|
877 | else | |
876 | sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}" |
|
878 | sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}" | |
877 | end |
|
879 | end | |
878 | else |
|
880 | else | |
879 | sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" |
|
881 | sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" | |
880 | end |
|
882 | end | |
881 | else |
|
883 | else | |
882 | # IN an empty set |
|
884 | # IN an empty set | |
883 | sql = "1=0" |
|
885 | sql = "1=0" | |
884 | end |
|
886 | end | |
885 | when "!" |
|
887 | when "!" | |
886 | if value.any? |
|
888 | if value.any? | |
887 | sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" |
|
889 | sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" | |
888 | else |
|
890 | else | |
889 | # NOT IN an empty set |
|
891 | # NOT IN an empty set | |
890 | sql = "1=1" |
|
892 | sql = "1=1" | |
891 | end |
|
893 | end | |
892 | when "!*" |
|
894 | when "!*" | |
893 | sql = "#{db_table}.#{db_field} IS NULL" |
|
895 | sql = "#{db_table}.#{db_field} IS NULL" | |
894 | sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter |
|
896 | sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter | |
895 | when "*" |
|
897 | when "*" | |
896 | sql = "#{db_table}.#{db_field} IS NOT NULL" |
|
898 | sql = "#{db_table}.#{db_field} IS NOT NULL" | |
897 | sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter |
|
899 | sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter | |
898 | when ">=" |
|
900 | when ">=" | |
899 | if [:date, :date_past].include?(type_for(field)) |
|
901 | if [:date, :date_past].include?(type_for(field)) | |
900 | sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil) |
|
902 | sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil) | |
901 | else |
|
903 | else | |
902 | if is_custom_filter |
|
904 | if is_custom_filter | |
903 | sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f})" |
|
905 | sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f})" | |
904 | else |
|
906 | else | |
905 | sql = "#{db_table}.#{db_field} >= #{value.first.to_f}" |
|
907 | sql = "#{db_table}.#{db_field} >= #{value.first.to_f}" | |
906 | end |
|
908 | end | |
907 | end |
|
909 | end | |
908 | when "<=" |
|
910 | when "<=" | |
909 | if [:date, :date_past].include?(type_for(field)) |
|
911 | if [:date, :date_past].include?(type_for(field)) | |
910 | sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil)) |
|
912 | sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil)) | |
911 | else |
|
913 | else | |
912 | if is_custom_filter |
|
914 | if is_custom_filter | |
913 | sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f})" |
|
915 | sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f})" | |
914 | else |
|
916 | else | |
915 | sql = "#{db_table}.#{db_field} <= #{value.first.to_f}" |
|
917 | sql = "#{db_table}.#{db_field} <= #{value.first.to_f}" | |
916 | end |
|
918 | end | |
917 | end |
|
919 | end | |
918 | when "><" |
|
920 | when "><" | |
919 | if [:date, :date_past].include?(type_for(field)) |
|
921 | if [:date, :date_past].include?(type_for(field)) | |
920 | sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil)) |
|
922 | sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil)) | |
921 | else |
|
923 | else | |
922 | if is_custom_filter |
|
924 | if is_custom_filter | |
923 | 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})" |
|
925 | 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})" | |
924 | else |
|
926 | else | |
925 | sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}" |
|
927 | sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}" | |
926 | end |
|
928 | end | |
927 | end |
|
929 | end | |
928 | when "o" |
|
930 | when "o" | |
929 | sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id" |
|
931 | sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id" | |
930 | when "c" |
|
932 | when "c" | |
931 | sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id" |
|
933 | sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id" | |
932 | when ">t-" |
|
934 | when ">t-" | |
933 | sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0) |
|
935 | sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0) | |
934 | when "<t-" |
|
936 | when "<t-" | |
935 | sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i) |
|
937 | sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i) | |
936 | when "t-" |
|
938 | when "t-" | |
937 | sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i) |
|
939 | sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i) | |
938 | when ">t+" |
|
940 | when ">t+" | |
939 | sql = relative_date_clause(db_table, db_field, value.first.to_i, nil) |
|
941 | sql = relative_date_clause(db_table, db_field, value.first.to_i, nil) | |
940 | when "<t+" |
|
942 | when "<t+" | |
941 | sql = relative_date_clause(db_table, db_field, 0, value.first.to_i) |
|
943 | sql = relative_date_clause(db_table, db_field, 0, value.first.to_i) | |
942 | when "t+" |
|
944 | when "t+" | |
943 | sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i) |
|
945 | sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i) | |
944 | when "t" |
|
946 | when "t" | |
945 | sql = relative_date_clause(db_table, db_field, 0, 0) |
|
947 | sql = relative_date_clause(db_table, db_field, 0, 0) | |
946 | when "w" |
|
948 | when "w" | |
947 | first_day_of_week = l(:general_first_day_of_week).to_i |
|
949 | first_day_of_week = l(:general_first_day_of_week).to_i | |
948 | day_of_week = Date.today.cwday |
|
950 | day_of_week = Date.today.cwday | |
949 | days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) |
|
951 | days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) | |
950 | sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6) |
|
952 | sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6) | |
951 | when "~" |
|
953 | when "~" | |
952 | sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" |
|
954 | sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" | |
953 | when "!~" |
|
955 | when "!~" | |
954 | sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" |
|
956 | sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" | |
955 | else |
|
957 | else | |
956 | raise "Unknown query operator #{operator}" |
|
958 | raise "Unknown query operator #{operator}" | |
957 | end |
|
959 | end | |
958 |
|
960 | |||
959 | return sql |
|
961 | return sql | |
960 | end |
|
962 | end | |
961 |
|
963 | |||
962 | def add_custom_fields_filters(custom_fields, assoc=nil) |
|
964 | def add_custom_fields_filters(custom_fields, assoc=nil) | |
963 | return unless custom_fields.present? |
|
965 | return unless custom_fields.present? | |
964 | @available_filters ||= {} |
|
966 | @available_filters ||= {} | |
965 |
|
967 | |||
966 | custom_fields.select(&:is_filter?).each do |field| |
|
968 | custom_fields.select(&:is_filter?).each do |field| | |
967 | case field.field_format |
|
969 | case field.field_format | |
968 | when "text" |
|
970 | when "text" | |
969 | options = { :type => :text, :order => 20 } |
|
971 | options = { :type => :text, :order => 20 } | |
970 | when "list" |
|
972 | when "list" | |
971 | options = { :type => :list_optional, :values => field.possible_values, :order => 20} |
|
973 | options = { :type => :list_optional, :values => field.possible_values, :order => 20} | |
972 | when "date" |
|
974 | when "date" | |
973 | options = { :type => :date, :order => 20 } |
|
975 | options = { :type => :date, :order => 20 } | |
974 | when "bool" |
|
976 | when "bool" | |
975 | options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } |
|
977 | options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } | |
976 | when "int" |
|
978 | when "int" | |
977 | options = { :type => :integer, :order => 20 } |
|
979 | options = { :type => :integer, :order => 20 } | |
978 | when "float" |
|
980 | when "float" | |
979 | options = { :type => :float, :order => 20 } |
|
981 | options = { :type => :float, :order => 20 } | |
980 | when "user", "version" |
|
982 | when "user", "version" | |
981 | next unless project |
|
983 | next unless project | |
982 | values = field.possible_values_options(project) |
|
984 | values = field.possible_values_options(project) | |
983 | if User.current.logged? && field.field_format == 'user' |
|
985 | if User.current.logged? && field.field_format == 'user' | |
984 | values.unshift ["<< #{l(:label_me)} >>", "me"] |
|
986 | values.unshift ["<< #{l(:label_me)} >>", "me"] | |
985 | end |
|
987 | end | |
986 | options = { :type => :list_optional, :values => values, :order => 20} |
|
988 | options = { :type => :list_optional, :values => values, :order => 20} | |
987 | else |
|
989 | else | |
988 | options = { :type => :string, :order => 20 } |
|
990 | options = { :type => :string, :order => 20 } | |
989 | end |
|
991 | end | |
990 | filter_id = "cf_#{field.id}" |
|
992 | filter_id = "cf_#{field.id}" | |
991 | filter_name = field.name |
|
993 | filter_name = field.name | |
992 | if assoc.present? |
|
994 | if assoc.present? | |
993 | filter_id = "#{assoc}.#{filter_id}" |
|
995 | filter_id = "#{assoc}.#{filter_id}" | |
994 | filter_name = l("label_attribute_of_#{assoc}", :name => filter_name) |
|
996 | filter_name = l("label_attribute_of_#{assoc}", :name => filter_name) | |
995 | end |
|
997 | end | |
996 | @available_filters[filter_id] = options.merge({ |
|
998 | @available_filters[filter_id] = options.merge({ | |
997 | :name => filter_name, |
|
999 | :name => filter_name, | |
998 | :format => field.field_format, |
|
1000 | :format => field.field_format, | |
999 | :field => field |
|
1001 | :field => field | |
1000 | }) |
|
1002 | }) | |
1001 | end |
|
1003 | end | |
1002 | end |
|
1004 | end | |
1003 |
|
1005 | |||
1004 | def add_associations_custom_fields_filters(*associations) |
|
1006 | def add_associations_custom_fields_filters(*associations) | |
1005 | fields_by_class = CustomField.where(:is_filter => true).group_by(&:class) |
|
1007 | fields_by_class = CustomField.where(:is_filter => true).group_by(&:class) | |
1006 | associations.each do |assoc| |
|
1008 | associations.each do |assoc| | |
1007 | association_klass = Issue.reflect_on_association(assoc).klass |
|
1009 | association_klass = Issue.reflect_on_association(assoc).klass | |
1008 | fields_by_class.each do |field_class, fields| |
|
1010 | fields_by_class.each do |field_class, fields| | |
1009 | if field_class.customized_class <= association_klass |
|
1011 | if field_class.customized_class <= association_klass | |
1010 | add_custom_fields_filters(fields, assoc) |
|
1012 | add_custom_fields_filters(fields, assoc) | |
1011 | end |
|
1013 | end | |
1012 | end |
|
1014 | end | |
1013 | end |
|
1015 | end | |
1014 | end |
|
1016 | end | |
1015 |
|
1017 | |||
1016 | # Returns a SQL clause for a date or datetime field. |
|
1018 | # Returns a SQL clause for a date or datetime field. | |
1017 | def date_clause(table, field, from, to) |
|
1019 | def date_clause(table, field, from, to) | |
1018 | s = [] |
|
1020 | s = [] | |
1019 | if from |
|
1021 | if from | |
1020 | from_yesterday = from - 1 |
|
1022 | from_yesterday = from - 1 | |
1021 | from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day) |
|
1023 | from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day) | |
1022 | if self.class.default_timezone == :utc |
|
1024 | if self.class.default_timezone == :utc | |
1023 | from_yesterday_time = from_yesterday_time.utc |
|
1025 | from_yesterday_time = from_yesterday_time.utc | |
1024 | end |
|
1026 | end | |
1025 | s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)]) |
|
1027 | s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)]) | |
1026 | end |
|
1028 | end | |
1027 | if to |
|
1029 | if to | |
1028 | to_time = Time.local(to.year, to.month, to.day) |
|
1030 | to_time = Time.local(to.year, to.month, to.day) | |
1029 | if self.class.default_timezone == :utc |
|
1031 | if self.class.default_timezone == :utc | |
1030 | to_time = to_time.utc |
|
1032 | to_time = to_time.utc | |
1031 | end |
|
1033 | end | |
1032 | s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)]) |
|
1034 | s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)]) | |
1033 | end |
|
1035 | end | |
1034 | s.join(' AND ') |
|
1036 | s.join(' AND ') | |
1035 | end |
|
1037 | end | |
1036 |
|
1038 | |||
1037 | # Returns a SQL clause for a date or datetime field using relative dates. |
|
1039 | # Returns a SQL clause for a date or datetime field using relative dates. | |
1038 | def relative_date_clause(table, field, days_from, days_to) |
|
1040 | def relative_date_clause(table, field, days_from, days_to) | |
1039 | date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil)) |
|
1041 | date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil)) | |
1040 | end |
|
1042 | end | |
1041 |
|
1043 | |||
1042 | # Additional joins required for the given sort options |
|
1044 | # Additional joins required for the given sort options | |
1043 | def joins_for_order_statement(order_options) |
|
1045 | def joins_for_order_statement(order_options) | |
1044 | joins = [] |
|
1046 | joins = [] | |
1045 |
|
1047 | |||
1046 | if order_options |
|
1048 | if order_options | |
1047 | if order_options.include?('authors') |
|
1049 | if order_options.include?('authors') | |
1048 | joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{Issue.table_name}.author_id" |
|
1050 | joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{Issue.table_name}.author_id" | |
1049 | end |
|
1051 | end | |
1050 | order_options.scan(/cf_\d+/).uniq.each do |name| |
|
1052 | order_options.scan(/cf_\d+/).uniq.each do |name| | |
1051 | column = available_columns.detect {|c| c.name.to_s == name} |
|
1053 | column = available_columns.detect {|c| c.name.to_s == name} | |
1052 | join = column && column.custom_field.join_for_order_statement |
|
1054 | join = column && column.custom_field.join_for_order_statement | |
1053 | if join |
|
1055 | if join | |
1054 | joins << join |
|
1056 | joins << join | |
1055 | end |
|
1057 | end | |
1056 | end |
|
1058 | end | |
1057 | end |
|
1059 | end | |
1058 |
|
1060 | |||
1059 | joins.any? ? joins.join(' ') : nil |
|
1061 | joins.any? ? joins.join(' ') : nil | |
1060 | end |
|
1062 | end | |
1061 | end |
|
1063 | end |
@@ -1,1069 +1,1070 | |||||
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 | field_private_notes: Private notes |
|
334 | field_private_notes: Private notes | |
335 |
|
335 | |||
336 | setting_app_title: Application title |
|
336 | setting_app_title: Application title | |
337 | setting_app_subtitle: Application subtitle |
|
337 | setting_app_subtitle: Application subtitle | |
338 | setting_welcome_text: Welcome text |
|
338 | setting_welcome_text: Welcome text | |
339 | setting_default_language: Default language |
|
339 | setting_default_language: Default language | |
340 | setting_login_required: Authentication required |
|
340 | setting_login_required: Authentication required | |
341 | setting_self_registration: Self-registration |
|
341 | setting_self_registration: Self-registration | |
342 | setting_attachment_max_size: Maximum attachment size |
|
342 | setting_attachment_max_size: Maximum attachment size | |
343 | setting_issues_export_limit: Issues export limit |
|
343 | setting_issues_export_limit: Issues export limit | |
344 | setting_mail_from: Emission email address |
|
344 | setting_mail_from: Emission email address | |
345 | setting_bcc_recipients: Blind carbon copy recipients (bcc) |
|
345 | setting_bcc_recipients: Blind carbon copy recipients (bcc) | |
346 | setting_plain_text_mail: Plain text mail (no HTML) |
|
346 | setting_plain_text_mail: Plain text mail (no HTML) | |
347 | setting_host_name: Host name and path |
|
347 | setting_host_name: Host name and path | |
348 | setting_text_formatting: Text formatting |
|
348 | setting_text_formatting: Text formatting | |
349 | setting_wiki_compression: Wiki history compression |
|
349 | setting_wiki_compression: Wiki history compression | |
350 | setting_feeds_limit: Maximum number of items in Atom feeds |
|
350 | setting_feeds_limit: Maximum number of items in Atom feeds | |
351 | setting_default_projects_public: New projects are public by default |
|
351 | setting_default_projects_public: New projects are public by default | |
352 | setting_autofetch_changesets: Fetch commits automatically |
|
352 | setting_autofetch_changesets: Fetch commits automatically | |
353 | setting_sys_api_enabled: Enable WS for repository management |
|
353 | setting_sys_api_enabled: Enable WS for repository management | |
354 | setting_commit_ref_keywords: Referencing keywords |
|
354 | setting_commit_ref_keywords: Referencing keywords | |
355 | setting_commit_fix_keywords: Fixing keywords |
|
355 | setting_commit_fix_keywords: Fixing keywords | |
356 | setting_autologin: Autologin |
|
356 | setting_autologin: Autologin | |
357 | setting_date_format: Date format |
|
357 | setting_date_format: Date format | |
358 | setting_time_format: Time format |
|
358 | setting_time_format: Time format | |
359 | setting_cross_project_issue_relations: Allow cross-project issue relations |
|
359 | setting_cross_project_issue_relations: Allow cross-project issue relations | |
360 | setting_issue_list_default_columns: Default columns displayed on the issue list |
|
360 | setting_issue_list_default_columns: Default columns displayed on the issue list | |
361 | setting_repositories_encodings: Attachments and repositories encodings |
|
361 | setting_repositories_encodings: Attachments and repositories encodings | |
362 | setting_emails_header: Emails header |
|
362 | setting_emails_header: Emails header | |
363 | setting_emails_footer: Emails footer |
|
363 | setting_emails_footer: Emails footer | |
364 | setting_protocol: Protocol |
|
364 | setting_protocol: Protocol | |
365 | setting_per_page_options: Objects per page options |
|
365 | setting_per_page_options: Objects per page options | |
366 | setting_user_format: Users display format |
|
366 | setting_user_format: Users display format | |
367 | setting_activity_days_default: Days displayed on project activity |
|
367 | setting_activity_days_default: Days displayed on project activity | |
368 | setting_display_subprojects_issues: Display subprojects issues on main projects by default |
|
368 | setting_display_subprojects_issues: Display subprojects issues on main projects by default | |
369 | setting_enabled_scm: Enabled SCM |
|
369 | setting_enabled_scm: Enabled SCM | |
370 | setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" |
|
370 | setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" | |
371 | setting_mail_handler_api_enabled: Enable WS for incoming emails |
|
371 | setting_mail_handler_api_enabled: Enable WS for incoming emails | |
372 | setting_mail_handler_api_key: API key |
|
372 | setting_mail_handler_api_key: API key | |
373 | setting_sequential_project_identifiers: Generate sequential project identifiers |
|
373 | setting_sequential_project_identifiers: Generate sequential project identifiers | |
374 | setting_gravatar_enabled: Use Gravatar user icons |
|
374 | setting_gravatar_enabled: Use Gravatar user icons | |
375 | setting_gravatar_default: Default Gravatar image |
|
375 | setting_gravatar_default: Default Gravatar image | |
376 | setting_diff_max_lines_displayed: Maximum number of diff lines displayed |
|
376 | setting_diff_max_lines_displayed: Maximum number of diff lines displayed | |
377 | setting_file_max_size_displayed: Maximum size of text files displayed inline |
|
377 | setting_file_max_size_displayed: Maximum size of text files displayed inline | |
378 | setting_repository_log_display_limit: Maximum number of revisions displayed on file log |
|
378 | setting_repository_log_display_limit: Maximum number of revisions displayed on file log | |
379 | setting_openid: Allow OpenID login and registration |
|
379 | setting_openid: Allow OpenID login and registration | |
380 | setting_password_min_length: Minimum password length |
|
380 | setting_password_min_length: Minimum password length | |
381 | setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
|
381 | setting_new_project_user_role_id: Role given to a non-admin user who creates a project | |
382 | setting_default_projects_modules: Default enabled modules for new projects |
|
382 | setting_default_projects_modules: Default enabled modules for new projects | |
383 | setting_issue_done_ratio: Calculate the issue done ratio with |
|
383 | setting_issue_done_ratio: Calculate the issue done ratio with | |
384 | setting_issue_done_ratio_issue_field: Use the issue field |
|
384 | setting_issue_done_ratio_issue_field: Use the issue field | |
385 | setting_issue_done_ratio_issue_status: Use the issue status |
|
385 | setting_issue_done_ratio_issue_status: Use the issue status | |
386 | setting_start_of_week: Start calendars on |
|
386 | setting_start_of_week: Start calendars on | |
387 | setting_rest_api_enabled: Enable REST web service |
|
387 | setting_rest_api_enabled: Enable REST web service | |
388 | setting_cache_formatted_text: Cache formatted text |
|
388 | setting_cache_formatted_text: Cache formatted text | |
389 | setting_default_notification_option: Default notification option |
|
389 | setting_default_notification_option: Default notification option | |
390 | setting_commit_logtime_enabled: Enable time logging |
|
390 | setting_commit_logtime_enabled: Enable time logging | |
391 | setting_commit_logtime_activity_id: Activity for logged time |
|
391 | setting_commit_logtime_activity_id: Activity for logged time | |
392 | setting_gantt_items_limit: Maximum number of items displayed on the gantt chart |
|
392 | setting_gantt_items_limit: Maximum number of items displayed on the gantt chart | |
393 | setting_issue_group_assignment: Allow issue assignment to groups |
|
393 | setting_issue_group_assignment: Allow issue assignment to groups | |
394 | setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues |
|
394 | setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues | |
395 | setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed |
|
395 | setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed | |
396 | setting_unsubscribe: Allow users to delete their own account |
|
396 | setting_unsubscribe: Allow users to delete their own account | |
397 | setting_session_lifetime: Session maximum lifetime |
|
397 | setting_session_lifetime: Session maximum lifetime | |
398 | setting_session_timeout: Session inactivity timeout |
|
398 | setting_session_timeout: Session inactivity timeout | |
399 | setting_thumbnails_enabled: Display attachment thumbnails |
|
399 | setting_thumbnails_enabled: Display attachment thumbnails | |
400 | setting_thumbnails_size: Thumbnails size (in pixels) |
|
400 | setting_thumbnails_size: Thumbnails size (in pixels) | |
401 |
|
401 | |||
402 | permission_add_project: Create project |
|
402 | permission_add_project: Create project | |
403 | permission_add_subprojects: Create subprojects |
|
403 | permission_add_subprojects: Create subprojects | |
404 | permission_edit_project: Edit project |
|
404 | permission_edit_project: Edit project | |
405 | permission_close_project: Close / reopen the project |
|
405 | permission_close_project: Close / reopen the project | |
406 | permission_select_project_modules: Select project modules |
|
406 | permission_select_project_modules: Select project modules | |
407 | permission_manage_members: Manage members |
|
407 | permission_manage_members: Manage members | |
408 | permission_manage_project_activities: Manage project activities |
|
408 | permission_manage_project_activities: Manage project activities | |
409 | permission_manage_versions: Manage versions |
|
409 | permission_manage_versions: Manage versions | |
410 | permission_manage_categories: Manage issue categories |
|
410 | permission_manage_categories: Manage issue categories | |
411 | permission_view_issues: View Issues |
|
411 | permission_view_issues: View Issues | |
412 | permission_add_issues: Add issues |
|
412 | permission_add_issues: Add issues | |
413 | permission_edit_issues: Edit issues |
|
413 | permission_edit_issues: Edit issues | |
414 | permission_manage_issue_relations: Manage issue relations |
|
414 | permission_manage_issue_relations: Manage issue relations | |
415 | permission_set_issues_private: Set issues public or private |
|
415 | permission_set_issues_private: Set issues public or private | |
416 | permission_set_own_issues_private: Set own issues public or private |
|
416 | permission_set_own_issues_private: Set own issues public or private | |
417 | permission_add_issue_notes: Add notes |
|
417 | permission_add_issue_notes: Add notes | |
418 | permission_edit_issue_notes: Edit notes |
|
418 | permission_edit_issue_notes: Edit notes | |
419 | permission_edit_own_issue_notes: Edit own notes |
|
419 | permission_edit_own_issue_notes: Edit own notes | |
420 | permission_view_private_notes: View private notes |
|
420 | permission_view_private_notes: View private notes | |
421 | permission_set_notes_private: Set notes as private |
|
421 | permission_set_notes_private: Set notes as private | |
422 | permission_move_issues: Move issues |
|
422 | permission_move_issues: Move issues | |
423 | permission_delete_issues: Delete issues |
|
423 | permission_delete_issues: Delete issues | |
424 | permission_manage_public_queries: Manage public queries |
|
424 | permission_manage_public_queries: Manage public queries | |
425 | permission_save_queries: Save queries |
|
425 | permission_save_queries: Save queries | |
426 | permission_view_gantt: View gantt chart |
|
426 | permission_view_gantt: View gantt chart | |
427 | permission_view_calendar: View calendar |
|
427 | permission_view_calendar: View calendar | |
428 | permission_view_issue_watchers: View watchers list |
|
428 | permission_view_issue_watchers: View watchers list | |
429 | permission_add_issue_watchers: Add watchers |
|
429 | permission_add_issue_watchers: Add watchers | |
430 | permission_delete_issue_watchers: Delete watchers |
|
430 | permission_delete_issue_watchers: Delete watchers | |
431 | permission_log_time: Log spent time |
|
431 | permission_log_time: Log spent time | |
432 | permission_view_time_entries: View spent time |
|
432 | permission_view_time_entries: View spent time | |
433 | permission_edit_time_entries: Edit time logs |
|
433 | permission_edit_time_entries: Edit time logs | |
434 | permission_edit_own_time_entries: Edit own time logs |
|
434 | permission_edit_own_time_entries: Edit own time logs | |
435 | permission_manage_news: Manage news |
|
435 | permission_manage_news: Manage news | |
436 | permission_comment_news: Comment news |
|
436 | permission_comment_news: Comment news | |
437 | permission_manage_documents: Manage documents |
|
437 | permission_manage_documents: Manage documents | |
438 | permission_view_documents: View documents |
|
438 | permission_view_documents: View documents | |
439 | permission_manage_files: Manage files |
|
439 | permission_manage_files: Manage files | |
440 | permission_view_files: View files |
|
440 | permission_view_files: View files | |
441 | permission_manage_wiki: Manage wiki |
|
441 | permission_manage_wiki: Manage wiki | |
442 | permission_rename_wiki_pages: Rename wiki pages |
|
442 | permission_rename_wiki_pages: Rename wiki pages | |
443 | permission_delete_wiki_pages: Delete wiki pages |
|
443 | permission_delete_wiki_pages: Delete wiki pages | |
444 | permission_view_wiki_pages: View wiki |
|
444 | permission_view_wiki_pages: View wiki | |
445 | permission_view_wiki_edits: View wiki history |
|
445 | permission_view_wiki_edits: View wiki history | |
446 | permission_edit_wiki_pages: Edit wiki pages |
|
446 | permission_edit_wiki_pages: Edit wiki pages | |
447 | permission_delete_wiki_pages_attachments: Delete attachments |
|
447 | permission_delete_wiki_pages_attachments: Delete attachments | |
448 | permission_protect_wiki_pages: Protect wiki pages |
|
448 | permission_protect_wiki_pages: Protect wiki pages | |
449 | permission_manage_repository: Manage repository |
|
449 | permission_manage_repository: Manage repository | |
450 | permission_browse_repository: Browse repository |
|
450 | permission_browse_repository: Browse repository | |
451 | permission_view_changesets: View changesets |
|
451 | permission_view_changesets: View changesets | |
452 | permission_commit_access: Commit access |
|
452 | permission_commit_access: Commit access | |
453 | permission_manage_boards: Manage forums |
|
453 | permission_manage_boards: Manage forums | |
454 | permission_view_messages: View messages |
|
454 | permission_view_messages: View messages | |
455 | permission_add_messages: Post messages |
|
455 | permission_add_messages: Post messages | |
456 | permission_edit_messages: Edit messages |
|
456 | permission_edit_messages: Edit messages | |
457 | permission_edit_own_messages: Edit own messages |
|
457 | permission_edit_own_messages: Edit own messages | |
458 | permission_delete_messages: Delete messages |
|
458 | permission_delete_messages: Delete messages | |
459 | permission_delete_own_messages: Delete own messages |
|
459 | permission_delete_own_messages: Delete own messages | |
460 | permission_export_wiki_pages: Export wiki pages |
|
460 | permission_export_wiki_pages: Export wiki pages | |
461 | permission_manage_subtasks: Manage subtasks |
|
461 | permission_manage_subtasks: Manage subtasks | |
462 | permission_manage_related_issues: Manage related issues |
|
462 | permission_manage_related_issues: Manage related issues | |
463 |
|
463 | |||
464 | project_module_issue_tracking: Issue tracking |
|
464 | project_module_issue_tracking: Issue tracking | |
465 | project_module_time_tracking: Time tracking |
|
465 | project_module_time_tracking: Time tracking | |
466 | project_module_news: News |
|
466 | project_module_news: News | |
467 | project_module_documents: Documents |
|
467 | project_module_documents: Documents | |
468 | project_module_files: Files |
|
468 | project_module_files: Files | |
469 | project_module_wiki: Wiki |
|
469 | project_module_wiki: Wiki | |
470 | project_module_repository: Repository |
|
470 | project_module_repository: Repository | |
471 | project_module_boards: Forums |
|
471 | project_module_boards: Forums | |
472 | project_module_calendar: Calendar |
|
472 | project_module_calendar: Calendar | |
473 | project_module_gantt: Gantt |
|
473 | project_module_gantt: Gantt | |
474 |
|
474 | |||
475 | label_user: User |
|
475 | label_user: User | |
476 | label_user_plural: Users |
|
476 | label_user_plural: Users | |
477 | label_user_new: New user |
|
477 | label_user_new: New user | |
478 | label_user_anonymous: Anonymous |
|
478 | label_user_anonymous: Anonymous | |
479 | label_project: Project |
|
479 | label_project: Project | |
480 | label_project_new: New project |
|
480 | label_project_new: New project | |
481 | label_project_plural: Projects |
|
481 | label_project_plural: Projects | |
482 | label_x_projects: |
|
482 | label_x_projects: | |
483 | zero: no projects |
|
483 | zero: no projects | |
484 | one: 1 project |
|
484 | one: 1 project | |
485 | other: "%{count} projects" |
|
485 | other: "%{count} projects" | |
486 | label_project_all: All Projects |
|
486 | label_project_all: All Projects | |
487 | label_project_latest: Latest projects |
|
487 | label_project_latest: Latest projects | |
488 | label_issue: Issue |
|
488 | label_issue: Issue | |
489 | label_issue_new: New issue |
|
489 | label_issue_new: New issue | |
490 | label_issue_plural: Issues |
|
490 | label_issue_plural: Issues | |
491 | label_issue_view_all: View all issues |
|
491 | label_issue_view_all: View all issues | |
492 | label_issues_by: "Issues by %{value}" |
|
492 | label_issues_by: "Issues by %{value}" | |
493 | label_issue_added: Issue added |
|
493 | label_issue_added: Issue added | |
494 | label_issue_updated: Issue updated |
|
494 | label_issue_updated: Issue updated | |
495 | label_issue_note_added: Note added |
|
495 | label_issue_note_added: Note added | |
496 | label_issue_status_updated: Status updated |
|
496 | label_issue_status_updated: Status updated | |
497 | label_issue_priority_updated: Priority updated |
|
497 | label_issue_priority_updated: Priority updated | |
498 | label_document: Document |
|
498 | label_document: Document | |
499 | label_document_new: New document |
|
499 | label_document_new: New document | |
500 | label_document_plural: Documents |
|
500 | label_document_plural: Documents | |
501 | label_document_added: Document added |
|
501 | label_document_added: Document added | |
502 | label_role: Role |
|
502 | label_role: Role | |
503 | label_role_plural: Roles |
|
503 | label_role_plural: Roles | |
504 | label_role_new: New role |
|
504 | label_role_new: New role | |
505 | label_role_and_permissions: Roles and permissions |
|
505 | label_role_and_permissions: Roles and permissions | |
506 | label_role_anonymous: Anonymous |
|
506 | label_role_anonymous: Anonymous | |
507 | label_role_non_member: Non member |
|
507 | label_role_non_member: Non member | |
508 | label_member: Member |
|
508 | label_member: Member | |
509 | label_member_new: New member |
|
509 | label_member_new: New member | |
510 | label_member_plural: Members |
|
510 | label_member_plural: Members | |
511 | label_tracker: Tracker |
|
511 | label_tracker: Tracker | |
512 | label_tracker_plural: Trackers |
|
512 | label_tracker_plural: Trackers | |
513 | label_tracker_new: New tracker |
|
513 | label_tracker_new: New tracker | |
514 | label_workflow: Workflow |
|
514 | label_workflow: Workflow | |
515 | label_issue_status: Issue status |
|
515 | label_issue_status: Issue status | |
516 | label_issue_status_plural: Issue statuses |
|
516 | label_issue_status_plural: Issue statuses | |
517 | label_issue_status_new: New status |
|
517 | label_issue_status_new: New status | |
518 | label_issue_category: Issue category |
|
518 | label_issue_category: Issue category | |
519 | label_issue_category_plural: Issue categories |
|
519 | label_issue_category_plural: Issue categories | |
520 | label_issue_category_new: New category |
|
520 | label_issue_category_new: New category | |
521 | label_custom_field: Custom field |
|
521 | label_custom_field: Custom field | |
522 | label_custom_field_plural: Custom fields |
|
522 | label_custom_field_plural: Custom fields | |
523 | label_custom_field_new: New custom field |
|
523 | label_custom_field_new: New custom field | |
524 | label_enumerations: Enumerations |
|
524 | label_enumerations: Enumerations | |
525 | label_enumeration_new: New value |
|
525 | label_enumeration_new: New value | |
526 | label_information: Information |
|
526 | label_information: Information | |
527 | label_information_plural: Information |
|
527 | label_information_plural: Information | |
528 | label_please_login: Please log in |
|
528 | label_please_login: Please log in | |
529 | label_register: Register |
|
529 | label_register: Register | |
530 | label_login_with_open_id_option: or login with OpenID |
|
530 | label_login_with_open_id_option: or login with OpenID | |
531 | label_password_lost: Lost password |
|
531 | label_password_lost: Lost password | |
532 | label_home: Home |
|
532 | label_home: Home | |
533 | label_my_page: My page |
|
533 | label_my_page: My page | |
534 | label_my_account: My account |
|
534 | label_my_account: My account | |
535 | label_my_projects: My projects |
|
535 | label_my_projects: My projects | |
536 | label_my_page_block: My page block |
|
536 | label_my_page_block: My page block | |
537 | label_administration: Administration |
|
537 | label_administration: Administration | |
538 | label_login: Sign in |
|
538 | label_login: Sign in | |
539 | label_logout: Sign out |
|
539 | label_logout: Sign out | |
540 | label_help: Help |
|
540 | label_help: Help | |
541 | label_reported_issues: Reported issues |
|
541 | label_reported_issues: Reported issues | |
542 | label_assigned_to_me_issues: Issues assigned to me |
|
542 | label_assigned_to_me_issues: Issues assigned to me | |
543 | label_last_login: Last connection |
|
543 | label_last_login: Last connection | |
544 | label_registered_on: Registered on |
|
544 | label_registered_on: Registered on | |
545 | label_activity: Activity |
|
545 | label_activity: Activity | |
546 | label_overall_activity: Overall activity |
|
546 | label_overall_activity: Overall activity | |
547 | label_user_activity: "%{value}'s activity" |
|
547 | label_user_activity: "%{value}'s activity" | |
548 | label_new: New |
|
548 | label_new: New | |
549 | label_logged_as: Logged in as |
|
549 | label_logged_as: Logged in as | |
550 | label_environment: Environment |
|
550 | label_environment: Environment | |
551 | label_authentication: Authentication |
|
551 | label_authentication: Authentication | |
552 | label_auth_source: Authentication mode |
|
552 | label_auth_source: Authentication mode | |
553 | label_auth_source_new: New authentication mode |
|
553 | label_auth_source_new: New authentication mode | |
554 | label_auth_source_plural: Authentication modes |
|
554 | label_auth_source_plural: Authentication modes | |
555 | label_subproject_plural: Subprojects |
|
555 | label_subproject_plural: Subprojects | |
556 | label_subproject_new: New subproject |
|
556 | label_subproject_new: New subproject | |
557 | label_and_its_subprojects: "%{value} and its subprojects" |
|
557 | label_and_its_subprojects: "%{value} and its subprojects" | |
558 | label_min_max_length: Min - Max length |
|
558 | label_min_max_length: Min - Max length | |
559 | label_list: List |
|
559 | label_list: List | |
560 | label_date: Date |
|
560 | label_date: Date | |
561 | label_integer: Integer |
|
561 | label_integer: Integer | |
562 | label_float: Float |
|
562 | label_float: Float | |
563 | label_boolean: Boolean |
|
563 | label_boolean: Boolean | |
564 | label_string: Text |
|
564 | label_string: Text | |
565 | label_text: Long text |
|
565 | label_text: Long text | |
566 | label_attribute: Attribute |
|
566 | label_attribute: Attribute | |
567 | label_attribute_plural: Attributes |
|
567 | label_attribute_plural: Attributes | |
568 | label_download: "%{count} Download" |
|
568 | label_download: "%{count} Download" | |
569 | label_download_plural: "%{count} Downloads" |
|
569 | label_download_plural: "%{count} Downloads" | |
570 | label_no_data: No data to display |
|
570 | label_no_data: No data to display | |
571 | label_change_status: Change status |
|
571 | label_change_status: Change status | |
572 | label_history: History |
|
572 | label_history: History | |
573 | label_attachment: File |
|
573 | label_attachment: File | |
574 | label_attachment_new: New file |
|
574 | label_attachment_new: New file | |
575 | label_attachment_delete: Delete file |
|
575 | label_attachment_delete: Delete file | |
576 | label_attachment_plural: Files |
|
576 | label_attachment_plural: Files | |
577 | label_file_added: File added |
|
577 | label_file_added: File added | |
578 | label_report: Report |
|
578 | label_report: Report | |
579 | label_report_plural: Reports |
|
579 | label_report_plural: Reports | |
580 | label_news: News |
|
580 | label_news: News | |
581 | label_news_new: Add news |
|
581 | label_news_new: Add news | |
582 | label_news_plural: News |
|
582 | label_news_plural: News | |
583 | label_news_latest: Latest news |
|
583 | label_news_latest: Latest news | |
584 | label_news_view_all: View all news |
|
584 | label_news_view_all: View all news | |
585 | label_news_added: News added |
|
585 | label_news_added: News added | |
586 | label_news_comment_added: Comment added to a news |
|
586 | label_news_comment_added: Comment added to a news | |
587 | label_settings: Settings |
|
587 | label_settings: Settings | |
588 | label_overview: Overview |
|
588 | label_overview: Overview | |
589 | label_version: Version |
|
589 | label_version: Version | |
590 | label_version_new: New version |
|
590 | label_version_new: New version | |
591 | label_version_plural: Versions |
|
591 | label_version_plural: Versions | |
592 | label_close_versions: Close completed versions |
|
592 | label_close_versions: Close completed versions | |
593 | label_confirmation: Confirmation |
|
593 | label_confirmation: Confirmation | |
594 | label_export_to: 'Also available in:' |
|
594 | label_export_to: 'Also available in:' | |
595 | label_read: Read... |
|
595 | label_read: Read... | |
596 | label_public_projects: Public projects |
|
596 | label_public_projects: Public projects | |
597 | label_open_issues: open |
|
597 | label_open_issues: open | |
598 | label_open_issues_plural: open |
|
598 | label_open_issues_plural: open | |
599 | label_closed_issues: closed |
|
599 | label_closed_issues: closed | |
600 | label_closed_issues_plural: closed |
|
600 | label_closed_issues_plural: closed | |
601 | label_x_open_issues_abbr_on_total: |
|
601 | label_x_open_issues_abbr_on_total: | |
602 | zero: 0 open / %{total} |
|
602 | zero: 0 open / %{total} | |
603 | one: 1 open / %{total} |
|
603 | one: 1 open / %{total} | |
604 | other: "%{count} open / %{total}" |
|
604 | other: "%{count} open / %{total}" | |
605 | label_x_open_issues_abbr: |
|
605 | label_x_open_issues_abbr: | |
606 | zero: 0 open |
|
606 | zero: 0 open | |
607 | one: 1 open |
|
607 | one: 1 open | |
608 | other: "%{count} open" |
|
608 | other: "%{count} open" | |
609 | label_x_closed_issues_abbr: |
|
609 | label_x_closed_issues_abbr: | |
610 | zero: 0 closed |
|
610 | zero: 0 closed | |
611 | one: 1 closed |
|
611 | one: 1 closed | |
612 | other: "%{count} closed" |
|
612 | other: "%{count} closed" | |
613 | label_x_issues: |
|
613 | label_x_issues: | |
614 | zero: 0 issues |
|
614 | zero: 0 issues | |
615 | one: 1 issue |
|
615 | one: 1 issue | |
616 | other: "%{count} issues" |
|
616 | other: "%{count} issues" | |
617 | label_total: Total |
|
617 | label_total: Total | |
618 | label_permissions: Permissions |
|
618 | label_permissions: Permissions | |
619 | label_current_status: Current status |
|
619 | label_current_status: Current status | |
620 | label_new_statuses_allowed: New statuses allowed |
|
620 | label_new_statuses_allowed: New statuses allowed | |
621 | label_all: all |
|
621 | label_all: all | |
622 | label_none: none |
|
622 | label_none: none | |
623 | label_nobody: nobody |
|
623 | label_nobody: nobody | |
624 | label_next: Next |
|
624 | label_next: Next | |
625 | label_previous: Previous |
|
625 | label_previous: Previous | |
626 | label_used_by: Used by |
|
626 | label_used_by: Used by | |
627 | label_details: Details |
|
627 | label_details: Details | |
628 | label_add_note: Add a note |
|
628 | label_add_note: Add a note | |
629 | label_per_page: Per page |
|
629 | label_per_page: Per page | |
630 | label_calendar: Calendar |
|
630 | label_calendar: Calendar | |
631 | label_months_from: months from |
|
631 | label_months_from: months from | |
632 | label_gantt: Gantt |
|
632 | label_gantt: Gantt | |
633 | label_internal: Internal |
|
633 | label_internal: Internal | |
634 | label_last_changes: "last %{count} changes" |
|
634 | label_last_changes: "last %{count} changes" | |
635 | label_change_view_all: View all changes |
|
635 | label_change_view_all: View all changes | |
636 | label_personalize_page: Personalize this page |
|
636 | label_personalize_page: Personalize this page | |
637 | label_comment: Comment |
|
637 | label_comment: Comment | |
638 | label_comment_plural: Comments |
|
638 | label_comment_plural: Comments | |
639 | label_x_comments: |
|
639 | label_x_comments: | |
640 | zero: no comments |
|
640 | zero: no comments | |
641 | one: 1 comment |
|
641 | one: 1 comment | |
642 | other: "%{count} comments" |
|
642 | other: "%{count} comments" | |
643 | label_comment_add: Add a comment |
|
643 | label_comment_add: Add a comment | |
644 | label_comment_added: Comment added |
|
644 | label_comment_added: Comment added | |
645 | label_comment_delete: Delete comments |
|
645 | label_comment_delete: Delete comments | |
646 | label_query: Custom query |
|
646 | label_query: Custom query | |
647 | label_query_plural: Custom queries |
|
647 | label_query_plural: Custom queries | |
648 | label_query_new: New query |
|
648 | label_query_new: New query | |
649 | label_my_queries: My custom queries |
|
649 | label_my_queries: My custom queries | |
650 | label_filter_add: Add filter |
|
650 | label_filter_add: Add filter | |
651 | label_filter_plural: Filters |
|
651 | label_filter_plural: Filters | |
652 | label_equals: is |
|
652 | label_equals: is | |
653 | label_not_equals: is not |
|
653 | label_not_equals: is not | |
654 | label_in_less_than: in less than |
|
654 | label_in_less_than: in less than | |
655 | label_in_more_than: in more than |
|
655 | label_in_more_than: in more than | |
656 | label_greater_or_equal: '>=' |
|
656 | label_greater_or_equal: '>=' | |
657 | label_less_or_equal: '<=' |
|
657 | label_less_or_equal: '<=' | |
658 | label_between: between |
|
658 | label_between: between | |
659 | label_in: in |
|
659 | label_in: in | |
660 | label_today: today |
|
660 | label_today: today | |
661 | label_all_time: all time |
|
661 | label_all_time: all time | |
662 | label_yesterday: yesterday |
|
662 | label_yesterday: yesterday | |
663 | label_this_week: this week |
|
663 | label_this_week: this week | |
664 | label_last_week: last week |
|
664 | label_last_week: last week | |
665 | label_last_n_days: "last %{count} days" |
|
665 | label_last_n_days: "last %{count} days" | |
666 | label_this_month: this month |
|
666 | label_this_month: this month | |
667 | label_last_month: last month |
|
667 | label_last_month: last month | |
668 | label_this_year: this year |
|
668 | label_this_year: this year | |
669 | label_date_range: Date range |
|
669 | label_date_range: Date range | |
670 | label_less_than_ago: less than days ago |
|
670 | label_less_than_ago: less than days ago | |
671 | label_more_than_ago: more than days ago |
|
671 | label_more_than_ago: more than days ago | |
672 | label_ago: days ago |
|
672 | label_ago: days ago | |
673 | label_contains: contains |
|
673 | label_contains: contains | |
674 | label_not_contains: doesn't contain |
|
674 | label_not_contains: doesn't contain | |
675 | label_any_issues_in_project: any issues in project |
|
675 | label_any_issues_in_project: any issues in project | |
676 | label_any_issues_not_in_project: any issues not in project |
|
676 | label_any_issues_not_in_project: any issues not in project | |
|
677 | label_no_issues_in_project: no issues in project | |||
677 | label_day_plural: days |
|
678 | label_day_plural: days | |
678 | label_repository: Repository |
|
679 | label_repository: Repository | |
679 | label_repository_new: New repository |
|
680 | label_repository_new: New repository | |
680 | label_repository_plural: Repositories |
|
681 | label_repository_plural: Repositories | |
681 | label_browse: Browse |
|
682 | label_browse: Browse | |
682 | label_modification: "%{count} change" |
|
683 | label_modification: "%{count} change" | |
683 | label_modification_plural: "%{count} changes" |
|
684 | label_modification_plural: "%{count} changes" | |
684 | label_branch: Branch |
|
685 | label_branch: Branch | |
685 | label_tag: Tag |
|
686 | label_tag: Tag | |
686 | label_revision: Revision |
|
687 | label_revision: Revision | |
687 | label_revision_plural: Revisions |
|
688 | label_revision_plural: Revisions | |
688 | label_revision_id: "Revision %{value}" |
|
689 | label_revision_id: "Revision %{value}" | |
689 | label_associated_revisions: Associated revisions |
|
690 | label_associated_revisions: Associated revisions | |
690 | label_added: added |
|
691 | label_added: added | |
691 | label_modified: modified |
|
692 | label_modified: modified | |
692 | label_copied: copied |
|
693 | label_copied: copied | |
693 | label_renamed: renamed |
|
694 | label_renamed: renamed | |
694 | label_deleted: deleted |
|
695 | label_deleted: deleted | |
695 | label_latest_revision: Latest revision |
|
696 | label_latest_revision: Latest revision | |
696 | label_latest_revision_plural: Latest revisions |
|
697 | label_latest_revision_plural: Latest revisions | |
697 | label_view_revisions: View revisions |
|
698 | label_view_revisions: View revisions | |
698 | label_view_all_revisions: View all revisions |
|
699 | label_view_all_revisions: View all revisions | |
699 | label_max_size: Maximum size |
|
700 | label_max_size: Maximum size | |
700 | label_sort_highest: Move to top |
|
701 | label_sort_highest: Move to top | |
701 | label_sort_higher: Move up |
|
702 | label_sort_higher: Move up | |
702 | label_sort_lower: Move down |
|
703 | label_sort_lower: Move down | |
703 | label_sort_lowest: Move to bottom |
|
704 | label_sort_lowest: Move to bottom | |
704 | label_roadmap: Roadmap |
|
705 | label_roadmap: Roadmap | |
705 | label_roadmap_due_in: "Due in %{value}" |
|
706 | label_roadmap_due_in: "Due in %{value}" | |
706 | label_roadmap_overdue: "%{value} late" |
|
707 | label_roadmap_overdue: "%{value} late" | |
707 | label_roadmap_no_issues: No issues for this version |
|
708 | label_roadmap_no_issues: No issues for this version | |
708 | label_search: Search |
|
709 | label_search: Search | |
709 | label_result_plural: Results |
|
710 | label_result_plural: Results | |
710 | label_all_words: All words |
|
711 | label_all_words: All words | |
711 | label_wiki: Wiki |
|
712 | label_wiki: Wiki | |
712 | label_wiki_edit: Wiki edit |
|
713 | label_wiki_edit: Wiki edit | |
713 | label_wiki_edit_plural: Wiki edits |
|
714 | label_wiki_edit_plural: Wiki edits | |
714 | label_wiki_page: Wiki page |
|
715 | label_wiki_page: Wiki page | |
715 | label_wiki_page_plural: Wiki pages |
|
716 | label_wiki_page_plural: Wiki pages | |
716 | label_index_by_title: Index by title |
|
717 | label_index_by_title: Index by title | |
717 | label_index_by_date: Index by date |
|
718 | label_index_by_date: Index by date | |
718 | label_current_version: Current version |
|
719 | label_current_version: Current version | |
719 | label_preview: Preview |
|
720 | label_preview: Preview | |
720 | label_feed_plural: Feeds |
|
721 | label_feed_plural: Feeds | |
721 | label_changes_details: Details of all changes |
|
722 | label_changes_details: Details of all changes | |
722 | label_issue_tracking: Issue tracking |
|
723 | label_issue_tracking: Issue tracking | |
723 | label_spent_time: Spent time |
|
724 | label_spent_time: Spent time | |
724 | label_overall_spent_time: Overall spent time |
|
725 | label_overall_spent_time: Overall spent time | |
725 | label_f_hour: "%{value} hour" |
|
726 | label_f_hour: "%{value} hour" | |
726 | label_f_hour_plural: "%{value} hours" |
|
727 | label_f_hour_plural: "%{value} hours" | |
727 | label_time_tracking: Time tracking |
|
728 | label_time_tracking: Time tracking | |
728 | label_change_plural: Changes |
|
729 | label_change_plural: Changes | |
729 | label_statistics: Statistics |
|
730 | label_statistics: Statistics | |
730 | label_commits_per_month: Commits per month |
|
731 | label_commits_per_month: Commits per month | |
731 | label_commits_per_author: Commits per author |
|
732 | label_commits_per_author: Commits per author | |
732 | label_diff: diff |
|
733 | label_diff: diff | |
733 | label_view_diff: View differences |
|
734 | label_view_diff: View differences | |
734 | label_diff_inline: inline |
|
735 | label_diff_inline: inline | |
735 | label_diff_side_by_side: side by side |
|
736 | label_diff_side_by_side: side by side | |
736 | label_options: Options |
|
737 | label_options: Options | |
737 | label_copy_workflow_from: Copy workflow from |
|
738 | label_copy_workflow_from: Copy workflow from | |
738 | label_permissions_report: Permissions report |
|
739 | label_permissions_report: Permissions report | |
739 | label_watched_issues: Watched issues |
|
740 | label_watched_issues: Watched issues | |
740 | label_related_issues: Related issues |
|
741 | label_related_issues: Related issues | |
741 | label_applied_status: Applied status |
|
742 | label_applied_status: Applied status | |
742 | label_loading: Loading... |
|
743 | label_loading: Loading... | |
743 | label_relation_new: New relation |
|
744 | label_relation_new: New relation | |
744 | label_relation_delete: Delete relation |
|
745 | label_relation_delete: Delete relation | |
745 | label_relates_to: Related to |
|
746 | label_relates_to: Related to | |
746 | label_duplicates: Duplicates |
|
747 | label_duplicates: Duplicates | |
747 | label_duplicated_by: Duplicated by |
|
748 | label_duplicated_by: Duplicated by | |
748 | label_blocks: Blocks |
|
749 | label_blocks: Blocks | |
749 | label_blocked_by: Blocked by |
|
750 | label_blocked_by: Blocked by | |
750 | label_precedes: Precedes |
|
751 | label_precedes: Precedes | |
751 | label_follows: Follows |
|
752 | label_follows: Follows | |
752 | label_copied_to: Copied to |
|
753 | label_copied_to: Copied to | |
753 | label_copied_from: Copied from |
|
754 | label_copied_from: Copied from | |
754 | label_end_to_start: end to start |
|
755 | label_end_to_start: end to start | |
755 | label_end_to_end: end to end |
|
756 | label_end_to_end: end to end | |
756 | label_start_to_start: start to start |
|
757 | label_start_to_start: start to start | |
757 | label_start_to_end: start to end |
|
758 | label_start_to_end: start to end | |
758 | label_stay_logged_in: Stay logged in |
|
759 | label_stay_logged_in: Stay logged in | |
759 | label_disabled: disabled |
|
760 | label_disabled: disabled | |
760 | label_show_completed_versions: Show completed versions |
|
761 | label_show_completed_versions: Show completed versions | |
761 | label_me: me |
|
762 | label_me: me | |
762 | label_board: Forum |
|
763 | label_board: Forum | |
763 | label_board_new: New forum |
|
764 | label_board_new: New forum | |
764 | label_board_plural: Forums |
|
765 | label_board_plural: Forums | |
765 | label_board_locked: Locked |
|
766 | label_board_locked: Locked | |
766 | label_board_sticky: Sticky |
|
767 | label_board_sticky: Sticky | |
767 | label_topic_plural: Topics |
|
768 | label_topic_plural: Topics | |
768 | label_message_plural: Messages |
|
769 | label_message_plural: Messages | |
769 | label_message_last: Last message |
|
770 | label_message_last: Last message | |
770 | label_message_new: New message |
|
771 | label_message_new: New message | |
771 | label_message_posted: Message added |
|
772 | label_message_posted: Message added | |
772 | label_reply_plural: Replies |
|
773 | label_reply_plural: Replies | |
773 | label_send_information: Send account information to the user |
|
774 | label_send_information: Send account information to the user | |
774 | label_year: Year |
|
775 | label_year: Year | |
775 | label_month: Month |
|
776 | label_month: Month | |
776 | label_week: Week |
|
777 | label_week: Week | |
777 | label_date_from: From |
|
778 | label_date_from: From | |
778 | label_date_to: To |
|
779 | label_date_to: To | |
779 | label_language_based: Based on user's language |
|
780 | label_language_based: Based on user's language | |
780 | label_sort_by: "Sort by %{value}" |
|
781 | label_sort_by: "Sort by %{value}" | |
781 | label_send_test_email: Send a test email |
|
782 | label_send_test_email: Send a test email | |
782 | label_feeds_access_key: RSS access key |
|
783 | label_feeds_access_key: RSS access key | |
783 | label_missing_feeds_access_key: Missing a RSS access key |
|
784 | label_missing_feeds_access_key: Missing a RSS access key | |
784 | label_feeds_access_key_created_on: "RSS access key created %{value} ago" |
|
785 | label_feeds_access_key_created_on: "RSS access key created %{value} ago" | |
785 | label_module_plural: Modules |
|
786 | label_module_plural: Modules | |
786 | label_added_time_by: "Added by %{author} %{age} ago" |
|
787 | label_added_time_by: "Added by %{author} %{age} ago" | |
787 | label_updated_time_by: "Updated by %{author} %{age} ago" |
|
788 | label_updated_time_by: "Updated by %{author} %{age} ago" | |
788 | label_updated_time: "Updated %{value} ago" |
|
789 | label_updated_time: "Updated %{value} ago" | |
789 | label_jump_to_a_project: Jump to a project... |
|
790 | label_jump_to_a_project: Jump to a project... | |
790 | label_file_plural: Files |
|
791 | label_file_plural: Files | |
791 | label_changeset_plural: Changesets |
|
792 | label_changeset_plural: Changesets | |
792 | label_default_columns: Default columns |
|
793 | label_default_columns: Default columns | |
793 | label_no_change_option: (No change) |
|
794 | label_no_change_option: (No change) | |
794 | label_bulk_edit_selected_issues: Bulk edit selected issues |
|
795 | label_bulk_edit_selected_issues: Bulk edit selected issues | |
795 | label_bulk_edit_selected_time_entries: Bulk edit selected time entries |
|
796 | label_bulk_edit_selected_time_entries: Bulk edit selected time entries | |
796 | label_theme: Theme |
|
797 | label_theme: Theme | |
797 | label_default: Default |
|
798 | label_default: Default | |
798 | label_search_titles_only: Search titles only |
|
799 | label_search_titles_only: Search titles only | |
799 | label_user_mail_option_all: "For any event on all my projects" |
|
800 | label_user_mail_option_all: "For any event on all my projects" | |
800 | label_user_mail_option_selected: "For any event on the selected projects only..." |
|
801 | label_user_mail_option_selected: "For any event on the selected projects only..." | |
801 | label_user_mail_option_none: "No events" |
|
802 | label_user_mail_option_none: "No events" | |
802 | label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in" |
|
803 | label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in" | |
803 | label_user_mail_option_only_assigned: "Only for things I am assigned to" |
|
804 | label_user_mail_option_only_assigned: "Only for things I am assigned to" | |
804 | label_user_mail_option_only_owner: "Only for things I am the owner of" |
|
805 | label_user_mail_option_only_owner: "Only for things I am the owner of" | |
805 | label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" |
|
806 | label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" | |
806 | label_registration_activation_by_email: account activation by email |
|
807 | label_registration_activation_by_email: account activation by email | |
807 | label_registration_manual_activation: manual account activation |
|
808 | label_registration_manual_activation: manual account activation | |
808 | label_registration_automatic_activation: automatic account activation |
|
809 | label_registration_automatic_activation: automatic account activation | |
809 | label_display_per_page: "Per page: %{value}" |
|
810 | label_display_per_page: "Per page: %{value}" | |
810 | label_age: Age |
|
811 | label_age: Age | |
811 | label_change_properties: Change properties |
|
812 | label_change_properties: Change properties | |
812 | label_general: General |
|
813 | label_general: General | |
813 | label_more: More |
|
814 | label_more: More | |
814 | label_scm: SCM |
|
815 | label_scm: SCM | |
815 | label_plugins: Plugins |
|
816 | label_plugins: Plugins | |
816 | label_ldap_authentication: LDAP authentication |
|
817 | label_ldap_authentication: LDAP authentication | |
817 | label_downloads_abbr: D/L |
|
818 | label_downloads_abbr: D/L | |
818 | label_optional_description: Optional description |
|
819 | label_optional_description: Optional description | |
819 | label_add_another_file: Add another file |
|
820 | label_add_another_file: Add another file | |
820 | label_preferences: Preferences |
|
821 | label_preferences: Preferences | |
821 | label_chronological_order: In chronological order |
|
822 | label_chronological_order: In chronological order | |
822 | label_reverse_chronological_order: In reverse chronological order |
|
823 | label_reverse_chronological_order: In reverse chronological order | |
823 | label_planning: Planning |
|
824 | label_planning: Planning | |
824 | label_incoming_emails: Incoming emails |
|
825 | label_incoming_emails: Incoming emails | |
825 | label_generate_key: Generate a key |
|
826 | label_generate_key: Generate a key | |
826 | label_issue_watchers: Watchers |
|
827 | label_issue_watchers: Watchers | |
827 | label_example: Example |
|
828 | label_example: Example | |
828 | label_display: Display |
|
829 | label_display: Display | |
829 | label_sort: Sort |
|
830 | label_sort: Sort | |
830 | label_ascending: Ascending |
|
831 | label_ascending: Ascending | |
831 | label_descending: Descending |
|
832 | label_descending: Descending | |
832 | label_date_from_to: From %{start} to %{end} |
|
833 | label_date_from_to: From %{start} to %{end} | |
833 | label_wiki_content_added: Wiki page added |
|
834 | label_wiki_content_added: Wiki page added | |
834 | label_wiki_content_updated: Wiki page updated |
|
835 | label_wiki_content_updated: Wiki page updated | |
835 | label_group: Group |
|
836 | label_group: Group | |
836 | label_group_plural: Groups |
|
837 | label_group_plural: Groups | |
837 | label_group_new: New group |
|
838 | label_group_new: New group | |
838 | label_time_entry_plural: Spent time |
|
839 | label_time_entry_plural: Spent time | |
839 | label_version_sharing_none: Not shared |
|
840 | label_version_sharing_none: Not shared | |
840 | label_version_sharing_descendants: With subprojects |
|
841 | label_version_sharing_descendants: With subprojects | |
841 | label_version_sharing_hierarchy: With project hierarchy |
|
842 | label_version_sharing_hierarchy: With project hierarchy | |
842 | label_version_sharing_tree: With project tree |
|
843 | label_version_sharing_tree: With project tree | |
843 | label_version_sharing_system: With all projects |
|
844 | label_version_sharing_system: With all projects | |
844 | label_update_issue_done_ratios: Update issue done ratios |
|
845 | label_update_issue_done_ratios: Update issue done ratios | |
845 | label_copy_source: Source |
|
846 | label_copy_source: Source | |
846 | label_copy_target: Target |
|
847 | label_copy_target: Target | |
847 | label_copy_same_as_target: Same as target |
|
848 | label_copy_same_as_target: Same as target | |
848 | label_display_used_statuses_only: Only display statuses that are used by this tracker |
|
849 | label_display_used_statuses_only: Only display statuses that are used by this tracker | |
849 | label_api_access_key: API access key |
|
850 | label_api_access_key: API access key | |
850 | label_missing_api_access_key: Missing an API access key |
|
851 | label_missing_api_access_key: Missing an API access key | |
851 | label_api_access_key_created_on: "API access key created %{value} ago" |
|
852 | label_api_access_key_created_on: "API access key created %{value} ago" | |
852 | label_profile: Profile |
|
853 | label_profile: Profile | |
853 | label_subtask_plural: Subtasks |
|
854 | label_subtask_plural: Subtasks | |
854 | label_project_copy_notifications: Send email notifications during the project copy |
|
855 | label_project_copy_notifications: Send email notifications during the project copy | |
855 | label_principal_search: "Search for user or group:" |
|
856 | label_principal_search: "Search for user or group:" | |
856 | label_user_search: "Search for user:" |
|
857 | label_user_search: "Search for user:" | |
857 | label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author |
|
858 | label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author | |
858 | label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee |
|
859 | label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee | |
859 | label_issues_visibility_all: All issues |
|
860 | label_issues_visibility_all: All issues | |
860 | label_issues_visibility_public: All non private issues |
|
861 | label_issues_visibility_public: All non private issues | |
861 | label_issues_visibility_own: Issues created by or assigned to the user |
|
862 | label_issues_visibility_own: Issues created by or assigned to the user | |
862 | label_git_report_last_commit: Report last commit for files and directories |
|
863 | label_git_report_last_commit: Report last commit for files and directories | |
863 | label_parent_revision: Parent |
|
864 | label_parent_revision: Parent | |
864 | label_child_revision: Child |
|
865 | label_child_revision: Child | |
865 | label_export_options: "%{export_format} export options" |
|
866 | label_export_options: "%{export_format} export options" | |
866 | label_copy_attachments: Copy attachments |
|
867 | label_copy_attachments: Copy attachments | |
867 | label_copy_subtasks: Copy subtasks |
|
868 | label_copy_subtasks: Copy subtasks | |
868 | label_item_position: "%{position} of %{count}" |
|
869 | label_item_position: "%{position} of %{count}" | |
869 | label_completed_versions: Completed versions |
|
870 | label_completed_versions: Completed versions | |
870 | label_search_for_watchers: Search for watchers to add |
|
871 | label_search_for_watchers: Search for watchers to add | |
871 | label_session_expiration: Session expiration |
|
872 | label_session_expiration: Session expiration | |
872 | label_show_closed_projects: View closed projects |
|
873 | label_show_closed_projects: View closed projects | |
873 | label_status_transitions: Status transitions |
|
874 | label_status_transitions: Status transitions | |
874 | label_fields_permissions: Fields permissions |
|
875 | label_fields_permissions: Fields permissions | |
875 | label_readonly: Read-only |
|
876 | label_readonly: Read-only | |
876 | label_required: Required |
|
877 | label_required: Required | |
877 | label_attribute_of_project: "Project's %{name}" |
|
878 | label_attribute_of_project: "Project's %{name}" | |
878 | label_attribute_of_author: "Author's %{name}" |
|
879 | label_attribute_of_author: "Author's %{name}" | |
879 | label_attribute_of_assigned_to: "Assignee's %{name}" |
|
880 | label_attribute_of_assigned_to: "Assignee's %{name}" | |
880 | label_attribute_of_fixed_version: "Target version's %{name}" |
|
881 | label_attribute_of_fixed_version: "Target version's %{name}" | |
881 |
|
882 | |||
882 | button_login: Login |
|
883 | button_login: Login | |
883 | button_submit: Submit |
|
884 | button_submit: Submit | |
884 | button_save: Save |
|
885 | button_save: Save | |
885 | button_check_all: Check all |
|
886 | button_check_all: Check all | |
886 | button_uncheck_all: Uncheck all |
|
887 | button_uncheck_all: Uncheck all | |
887 | button_collapse_all: Collapse all |
|
888 | button_collapse_all: Collapse all | |
888 | button_expand_all: Expand all |
|
889 | button_expand_all: Expand all | |
889 | button_delete: Delete |
|
890 | button_delete: Delete | |
890 | button_create: Create |
|
891 | button_create: Create | |
891 | button_create_and_continue: Create and continue |
|
892 | button_create_and_continue: Create and continue | |
892 | button_test: Test |
|
893 | button_test: Test | |
893 | button_edit: Edit |
|
894 | button_edit: Edit | |
894 | button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" |
|
895 | button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" | |
895 | button_add: Add |
|
896 | button_add: Add | |
896 | button_change: Change |
|
897 | button_change: Change | |
897 | button_apply: Apply |
|
898 | button_apply: Apply | |
898 | button_clear: Clear |
|
899 | button_clear: Clear | |
899 | button_lock: Lock |
|
900 | button_lock: Lock | |
900 | button_unlock: Unlock |
|
901 | button_unlock: Unlock | |
901 | button_download: Download |
|
902 | button_download: Download | |
902 | button_list: List |
|
903 | button_list: List | |
903 | button_view: View |
|
904 | button_view: View | |
904 | button_move: Move |
|
905 | button_move: Move | |
905 | button_move_and_follow: Move and follow |
|
906 | button_move_and_follow: Move and follow | |
906 | button_back: Back |
|
907 | button_back: Back | |
907 | button_cancel: Cancel |
|
908 | button_cancel: Cancel | |
908 | button_activate: Activate |
|
909 | button_activate: Activate | |
909 | button_sort: Sort |
|
910 | button_sort: Sort | |
910 | button_log_time: Log time |
|
911 | button_log_time: Log time | |
911 | button_rollback: Rollback to this version |
|
912 | button_rollback: Rollback to this version | |
912 | button_watch: Watch |
|
913 | button_watch: Watch | |
913 | button_unwatch: Unwatch |
|
914 | button_unwatch: Unwatch | |
914 | button_reply: Reply |
|
915 | button_reply: Reply | |
915 | button_archive: Archive |
|
916 | button_archive: Archive | |
916 | button_unarchive: Unarchive |
|
917 | button_unarchive: Unarchive | |
917 | button_reset: Reset |
|
918 | button_reset: Reset | |
918 | button_rename: Rename |
|
919 | button_rename: Rename | |
919 | button_change_password: Change password |
|
920 | button_change_password: Change password | |
920 | button_copy: Copy |
|
921 | button_copy: Copy | |
921 | button_copy_and_follow: Copy and follow |
|
922 | button_copy_and_follow: Copy and follow | |
922 | button_annotate: Annotate |
|
923 | button_annotate: Annotate | |
923 | button_update: Update |
|
924 | button_update: Update | |
924 | button_configure: Configure |
|
925 | button_configure: Configure | |
925 | button_quote: Quote |
|
926 | button_quote: Quote | |
926 | button_duplicate: Duplicate |
|
927 | button_duplicate: Duplicate | |
927 | button_show: Show |
|
928 | button_show: Show | |
928 | button_edit_section: Edit this section |
|
929 | button_edit_section: Edit this section | |
929 | button_export: Export |
|
930 | button_export: Export | |
930 | button_delete_my_account: Delete my account |
|
931 | button_delete_my_account: Delete my account | |
931 | button_close: Close |
|
932 | button_close: Close | |
932 | button_reopen: Reopen |
|
933 | button_reopen: Reopen | |
933 |
|
934 | |||
934 | status_active: active |
|
935 | status_active: active | |
935 | status_registered: registered |
|
936 | status_registered: registered | |
936 | status_locked: locked |
|
937 | status_locked: locked | |
937 |
|
938 | |||
938 | project_status_active: active |
|
939 | project_status_active: active | |
939 | project_status_closed: closed |
|
940 | project_status_closed: closed | |
940 | project_status_archived: archived |
|
941 | project_status_archived: archived | |
941 |
|
942 | |||
942 | version_status_open: open |
|
943 | version_status_open: open | |
943 | version_status_locked: locked |
|
944 | version_status_locked: locked | |
944 | version_status_closed: closed |
|
945 | version_status_closed: closed | |
945 |
|
946 | |||
946 | field_active: Active |
|
947 | field_active: Active | |
947 |
|
948 | |||
948 | text_select_mail_notifications: Select actions for which email notifications should be sent. |
|
949 | text_select_mail_notifications: Select actions for which email notifications should be sent. | |
949 | text_regexp_info: eg. ^[A-Z0-9]+$ |
|
950 | text_regexp_info: eg. ^[A-Z0-9]+$ | |
950 | text_min_max_length_info: 0 means no restriction |
|
951 | text_min_max_length_info: 0 means no restriction | |
951 | text_project_destroy_confirmation: Are you sure you want to delete this project and related data? |
|
952 | text_project_destroy_confirmation: Are you sure you want to delete this project and related data? | |
952 | text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." |
|
953 | text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." | |
953 | text_workflow_edit: Select a role and a tracker to edit the workflow |
|
954 | text_workflow_edit: Select a role and a tracker to edit the workflow | |
954 | text_are_you_sure: Are you sure? |
|
955 | text_are_you_sure: Are you sure? | |
955 | text_are_you_sure_with_children: "Delete issue and all child issues?" |
|
956 | text_are_you_sure_with_children: "Delete issue and all child issues?" | |
956 | text_journal_changed: "%{label} changed from %{old} to %{new}" |
|
957 | text_journal_changed: "%{label} changed from %{old} to %{new}" | |
957 | text_journal_changed_no_detail: "%{label} updated" |
|
958 | text_journal_changed_no_detail: "%{label} updated" | |
958 | text_journal_set_to: "%{label} set to %{value}" |
|
959 | text_journal_set_to: "%{label} set to %{value}" | |
959 | text_journal_deleted: "%{label} deleted (%{old})" |
|
960 | text_journal_deleted: "%{label} deleted (%{old})" | |
960 | text_journal_added: "%{label} %{value} added" |
|
961 | text_journal_added: "%{label} %{value} added" | |
961 | text_tip_issue_begin_day: issue beginning this day |
|
962 | text_tip_issue_begin_day: issue beginning this day | |
962 | text_tip_issue_end_day: issue ending this day |
|
963 | text_tip_issue_end_day: issue ending this day | |
963 | text_tip_issue_begin_end_day: issue beginning and ending this day |
|
964 | text_tip_issue_begin_end_day: issue beginning and ending this day | |
964 | text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.' |
|
965 | text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.' | |
965 | text_caracters_maximum: "%{count} characters maximum." |
|
966 | text_caracters_maximum: "%{count} characters maximum." | |
966 | text_caracters_minimum: "Must be at least %{count} characters long." |
|
967 | text_caracters_minimum: "Must be at least %{count} characters long." | |
967 | text_length_between: "Length between %{min} and %{max} characters." |
|
968 | text_length_between: "Length between %{min} and %{max} characters." | |
968 | text_tracker_no_workflow: No workflow defined for this tracker |
|
969 | text_tracker_no_workflow: No workflow defined for this tracker | |
969 | text_unallowed_characters: Unallowed characters |
|
970 | text_unallowed_characters: Unallowed characters | |
970 | text_comma_separated: Multiple values allowed (comma separated). |
|
971 | text_comma_separated: Multiple values allowed (comma separated). | |
971 | text_line_separated: Multiple values allowed (one line for each value). |
|
972 | text_line_separated: Multiple values allowed (one line for each value). | |
972 | text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages |
|
973 | text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages | |
973 | text_issue_added: "Issue %{id} has been reported by %{author}." |
|
974 | text_issue_added: "Issue %{id} has been reported by %{author}." | |
974 | text_issue_updated: "Issue %{id} has been updated by %{author}." |
|
975 | text_issue_updated: "Issue %{id} has been updated by %{author}." | |
975 | text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content? |
|
976 | text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content? | |
976 | text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?" |
|
977 | text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?" | |
977 | text_issue_category_destroy_assignments: Remove category assignments |
|
978 | text_issue_category_destroy_assignments: Remove category assignments | |
978 | text_issue_category_reassign_to: Reassign issues to this category |
|
979 | text_issue_category_reassign_to: Reassign issues to this category | |
979 | 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)." |
|
980 | 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)." | |
980 | 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." |
|
981 | 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." | |
981 | text_load_default_configuration: Load the default configuration |
|
982 | text_load_default_configuration: Load the default configuration | |
982 | text_status_changed_by_changeset: "Applied in changeset %{value}." |
|
983 | text_status_changed_by_changeset: "Applied in changeset %{value}." | |
983 | text_time_logged_by_changeset: "Applied in changeset %{value}." |
|
984 | text_time_logged_by_changeset: "Applied in changeset %{value}." | |
984 | text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?' |
|
985 | text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?' | |
985 | text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)." |
|
986 | text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)." | |
986 | text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?' |
|
987 | text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?' | |
987 | text_select_project_modules: 'Select modules to enable for this project:' |
|
988 | text_select_project_modules: 'Select modules to enable for this project:' | |
988 | text_default_administrator_account_changed: Default administrator account changed |
|
989 | text_default_administrator_account_changed: Default administrator account changed | |
989 | text_file_repository_writable: Attachments directory writable |
|
990 | text_file_repository_writable: Attachments directory writable | |
990 | text_plugin_assets_writable: Plugin assets directory writable |
|
991 | text_plugin_assets_writable: Plugin assets directory writable | |
991 | text_rmagick_available: RMagick available (optional) |
|
992 | text_rmagick_available: RMagick available (optional) | |
992 | text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?" |
|
993 | text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?" | |
993 | text_destroy_time_entries: Delete reported hours |
|
994 | text_destroy_time_entries: Delete reported hours | |
994 | text_assign_time_entries_to_project: Assign reported hours to the project |
|
995 | text_assign_time_entries_to_project: Assign reported hours to the project | |
995 | text_reassign_time_entries: 'Reassign reported hours to this issue:' |
|
996 | text_reassign_time_entries: 'Reassign reported hours to this issue:' | |
996 | text_user_wrote: "%{value} wrote:" |
|
997 | text_user_wrote: "%{value} wrote:" | |
997 | text_enumeration_destroy_question: "%{count} objects are assigned to this value." |
|
998 | text_enumeration_destroy_question: "%{count} objects are assigned to this value." | |
998 | text_enumeration_category_reassign_to: 'Reassign them to this value:' |
|
999 | text_enumeration_category_reassign_to: 'Reassign them to this value:' | |
999 | 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." |
|
1000 | 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." | |
1000 | 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." |
|
1001 | 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." | |
1001 | text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' |
|
1002 | text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' | |
1002 | text_custom_field_possible_values_info: 'One line for each value' |
|
1003 | text_custom_field_possible_values_info: 'One line for each value' | |
1003 | text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" |
|
1004 | text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" | |
1004 | text_wiki_page_nullify_children: "Keep child pages as root pages" |
|
1005 | text_wiki_page_nullify_children: "Keep child pages as root pages" | |
1005 | text_wiki_page_destroy_children: "Delete child pages and all their descendants" |
|
1006 | text_wiki_page_destroy_children: "Delete child pages and all their descendants" | |
1006 | text_wiki_page_reassign_children: "Reassign child pages to this parent page" |
|
1007 | text_wiki_page_reassign_children: "Reassign child pages to this parent page" | |
1007 | 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?" |
|
1008 | 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?" | |
1008 | text_zoom_in: Zoom in |
|
1009 | text_zoom_in: Zoom in | |
1009 | text_zoom_out: Zoom out |
|
1010 | text_zoom_out: Zoom out | |
1010 | text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page." |
|
1011 | text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page." | |
1011 | text_scm_path_encoding_note: "Default: UTF-8" |
|
1012 | text_scm_path_encoding_note: "Default: UTF-8" | |
1012 | text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) |
|
1013 | text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) | |
1013 | text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) |
|
1014 | text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) | |
1014 | text_scm_command: Command |
|
1015 | text_scm_command: Command | |
1015 | text_scm_command_version: Version |
|
1016 | text_scm_command_version: Version | |
1016 | text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. |
|
1017 | text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. | |
1017 | text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. |
|
1018 | text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. | |
1018 | text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)" |
|
1019 | text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)" | |
1019 | text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes" |
|
1020 | text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes" | |
1020 | text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}" |
|
1021 | text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}" | |
1021 | text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it." |
|
1022 | text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it." | |
1022 | text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." |
|
1023 | text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." | |
1023 | text_project_closed: This project is closed and read-only. |
|
1024 | text_project_closed: This project is closed and read-only. | |
1024 |
|
1025 | |||
1025 | default_role_manager: Manager |
|
1026 | default_role_manager: Manager | |
1026 | default_role_developer: Developer |
|
1027 | default_role_developer: Developer | |
1027 | default_role_reporter: Reporter |
|
1028 | default_role_reporter: Reporter | |
1028 | default_tracker_bug: Bug |
|
1029 | default_tracker_bug: Bug | |
1029 | default_tracker_feature: Feature |
|
1030 | default_tracker_feature: Feature | |
1030 | default_tracker_support: Support |
|
1031 | default_tracker_support: Support | |
1031 | default_issue_status_new: New |
|
1032 | default_issue_status_new: New | |
1032 | default_issue_status_in_progress: In Progress |
|
1033 | default_issue_status_in_progress: In Progress | |
1033 | default_issue_status_resolved: Resolved |
|
1034 | default_issue_status_resolved: Resolved | |
1034 | default_issue_status_feedback: Feedback |
|
1035 | default_issue_status_feedback: Feedback | |
1035 | default_issue_status_closed: Closed |
|
1036 | default_issue_status_closed: Closed | |
1036 | default_issue_status_rejected: Rejected |
|
1037 | default_issue_status_rejected: Rejected | |
1037 | default_doc_category_user: User documentation |
|
1038 | default_doc_category_user: User documentation | |
1038 | default_doc_category_tech: Technical documentation |
|
1039 | default_doc_category_tech: Technical documentation | |
1039 | default_priority_low: Low |
|
1040 | default_priority_low: Low | |
1040 | default_priority_normal: Normal |
|
1041 | default_priority_normal: Normal | |
1041 | default_priority_high: High |
|
1042 | default_priority_high: High | |
1042 | default_priority_urgent: Urgent |
|
1043 | default_priority_urgent: Urgent | |
1043 | default_priority_immediate: Immediate |
|
1044 | default_priority_immediate: Immediate | |
1044 | default_activity_design: Design |
|
1045 | default_activity_design: Design | |
1045 | default_activity_development: Development |
|
1046 | default_activity_development: Development | |
1046 |
|
1047 | |||
1047 | enumeration_issue_priorities: Issue priorities |
|
1048 | enumeration_issue_priorities: Issue priorities | |
1048 | enumeration_doc_categories: Document categories |
|
1049 | enumeration_doc_categories: Document categories | |
1049 | enumeration_activities: Activities (time tracking) |
|
1050 | enumeration_activities: Activities (time tracking) | |
1050 | enumeration_system_activity: System Activity |
|
1051 | enumeration_system_activity: System Activity | |
1051 | description_filter: Filter |
|
1052 | description_filter: Filter | |
1052 | description_search: Searchfield |
|
1053 | description_search: Searchfield | |
1053 | description_choose_project: Projects |
|
1054 | description_choose_project: Projects | |
1054 | description_project_scope: Search scope |
|
1055 | description_project_scope: Search scope | |
1055 | description_notes: Notes |
|
1056 | description_notes: Notes | |
1056 | description_message_content: Message content |
|
1057 | description_message_content: Message content | |
1057 | description_query_sort_criteria_attribute: Sort attribute |
|
1058 | description_query_sort_criteria_attribute: Sort attribute | |
1058 | description_query_sort_criteria_direction: Sort direction |
|
1059 | description_query_sort_criteria_direction: Sort direction | |
1059 | description_user_mail_notification: Mail notification settings |
|
1060 | description_user_mail_notification: Mail notification settings | |
1060 | description_available_columns: Available Columns |
|
1061 | description_available_columns: Available Columns | |
1061 | description_selected_columns: Selected Columns |
|
1062 | description_selected_columns: Selected Columns | |
1062 | description_all_columns: All Columns |
|
1063 | description_all_columns: All Columns | |
1063 | description_issue_category_reassign: Choose issue category |
|
1064 | description_issue_category_reassign: Choose issue category | |
1064 | description_wiki_subpages_reassign: Choose new parent page |
|
1065 | description_wiki_subpages_reassign: Choose new parent page | |
1065 | description_date_range_list: Choose range from list |
|
1066 | description_date_range_list: Choose range from list | |
1066 | description_date_range_interval: Choose range by selecting start and end date |
|
1067 | description_date_range_interval: Choose range by selecting start and end date | |
1067 | description_date_from: Enter start date |
|
1068 | description_date_from: Enter start date | |
1068 | description_date_to: Enter end date |
|
1069 | description_date_to: Enter end date | |
1069 | text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.' |
|
1070 | 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,1086 +1,1087 | |||||
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 | field_private_notes: Notes privΓ©es |
|
333 | field_private_notes: Notes privΓ©es | |
334 |
|
334 | |||
335 | setting_app_title: Titre de l'application |
|
335 | setting_app_title: Titre de l'application | |
336 | setting_app_subtitle: Sous-titre de l'application |
|
336 | setting_app_subtitle: Sous-titre de l'application | |
337 | setting_welcome_text: Texte d'accueil |
|
337 | setting_welcome_text: Texte d'accueil | |
338 | setting_default_language: Langue par dΓ©faut |
|
338 | setting_default_language: Langue par dΓ©faut | |
339 | setting_login_required: Authentification obligatoire |
|
339 | setting_login_required: Authentification obligatoire | |
340 | setting_self_registration: Inscription des nouveaux utilisateurs |
|
340 | setting_self_registration: Inscription des nouveaux utilisateurs | |
341 | setting_attachment_max_size: Taille maximale des fichiers |
|
341 | setting_attachment_max_size: Taille maximale des fichiers | |
342 | setting_issues_export_limit: Limite d'exportation des demandes |
|
342 | setting_issues_export_limit: Limite d'exportation des demandes | |
343 | setting_mail_from: Adresse d'Γ©mission |
|
343 | setting_mail_from: Adresse d'Γ©mission | |
344 | setting_bcc_recipients: Destinataires en copie cachΓ©e (cci) |
|
344 | setting_bcc_recipients: Destinataires en copie cachΓ©e (cci) | |
345 | setting_plain_text_mail: Mail en texte brut (non HTML) |
|
345 | setting_plain_text_mail: Mail en texte brut (non HTML) | |
346 | setting_host_name: Nom d'hΓ΄te et chemin |
|
346 | setting_host_name: Nom d'hΓ΄te et chemin | |
347 | setting_text_formatting: Formatage du texte |
|
347 | setting_text_formatting: Formatage du texte | |
348 | setting_wiki_compression: Compression de l'historique des pages wiki |
|
348 | setting_wiki_compression: Compression de l'historique des pages wiki | |
349 | setting_feeds_limit: Nombre maximal d'Γ©lΓ©ments dans les flux Atom |
|
349 | setting_feeds_limit: Nombre maximal d'Γ©lΓ©ments dans les flux Atom | |
350 | setting_default_projects_public: DΓ©finir les nouveaux projets comme publics par dΓ©faut |
|
350 | setting_default_projects_public: DΓ©finir les nouveaux projets comme publics par dΓ©faut | |
351 | setting_autofetch_changesets: RΓ©cupΓ©ration automatique des commits |
|
351 | setting_autofetch_changesets: RΓ©cupΓ©ration automatique des commits | |
352 | setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts |
|
352 | setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts | |
353 | setting_commit_ref_keywords: Mots-clΓ©s de rΓ©fΓ©rencement |
|
353 | setting_commit_ref_keywords: Mots-clΓ©s de rΓ©fΓ©rencement | |
354 | setting_commit_fix_keywords: Mots-clΓ©s de rΓ©solution |
|
354 | setting_commit_fix_keywords: Mots-clΓ©s de rΓ©solution | |
355 | setting_autologin: DurΓ©e maximale de connexion automatique |
|
355 | setting_autologin: DurΓ©e maximale de connexion automatique | |
356 | setting_date_format: Format de date |
|
356 | setting_date_format: Format de date | |
357 | setting_time_format: Format d'heure |
|
357 | setting_time_format: Format d'heure | |
358 | setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets |
|
358 | setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets | |
359 | setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes |
|
359 | setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes | |
360 | setting_emails_footer: Pied-de-page des emails |
|
360 | setting_emails_footer: Pied-de-page des emails | |
361 | setting_protocol: Protocole |
|
361 | setting_protocol: Protocole | |
362 | setting_per_page_options: Options d'objets affichΓ©s par page |
|
362 | setting_per_page_options: Options d'objets affichΓ©s par page | |
363 | setting_user_format: Format d'affichage des utilisateurs |
|
363 | setting_user_format: Format d'affichage des utilisateurs | |
364 | setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets |
|
364 | setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets | |
365 | setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux |
|
365 | setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux | |
366 | setting_enabled_scm: SCM activΓ©s |
|
366 | setting_enabled_scm: SCM activΓ©s | |
367 | setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes" |
|
367 | setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes" | |
368 | setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails" |
|
368 | setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails" | |
369 | setting_mail_handler_api_key: ClΓ© de protection de l'API |
|
369 | setting_mail_handler_api_key: ClΓ© de protection de l'API | |
370 | setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels |
|
370 | setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels | |
371 | setting_gravatar_enabled: Afficher les Gravatar des utilisateurs |
|
371 | setting_gravatar_enabled: Afficher les Gravatar des utilisateurs | |
372 | setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es |
|
372 | setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es | |
373 | setting_file_max_size_displayed: Taille maximum des fichiers texte affichΓ©s en ligne |
|
373 | setting_file_max_size_displayed: Taille maximum des fichiers texte affichΓ©s en ligne | |
374 | setting_repository_log_display_limit: "Nombre maximum de rΓ©visions affichΓ©es sur l'historique d'un fichier" |
|
374 | setting_repository_log_display_limit: "Nombre maximum de rΓ©visions affichΓ©es sur l'historique d'un fichier" | |
375 | setting_openid: "Autoriser l'authentification et l'enregistrement OpenID" |
|
375 | setting_openid: "Autoriser l'authentification et l'enregistrement OpenID" | |
376 | setting_password_min_length: Longueur minimum des mots de passe |
|
376 | setting_password_min_length: Longueur minimum des mots de passe | |
377 | setting_new_project_user_role_id: RΓ΄le donnΓ© Γ un utilisateur non-administrateur qui crΓ©e un projet |
|
377 | setting_new_project_user_role_id: RΓ΄le donnΓ© Γ un utilisateur non-administrateur qui crΓ©e un projet | |
378 | setting_default_projects_modules: Modules activΓ©s par dΓ©faut pour les nouveaux projets |
|
378 | setting_default_projects_modules: Modules activΓ©s par dΓ©faut pour les nouveaux projets | |
379 | setting_issue_done_ratio: Calcul de l'avancement des demandes |
|
379 | setting_issue_done_ratio: Calcul de l'avancement des demandes | |
380 | setting_issue_done_ratio_issue_status: Utiliser le statut |
|
380 | setting_issue_done_ratio_issue_status: Utiliser le statut | |
381 | setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectuΓ©' |
|
381 | setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectuΓ©' | |
382 | setting_rest_api_enabled: Activer l'API REST |
|
382 | setting_rest_api_enabled: Activer l'API REST | |
383 | setting_gravatar_default: Image Gravatar par dΓ©faut |
|
383 | setting_gravatar_default: Image Gravatar par dΓ©faut | |
384 | setting_start_of_week: Jour de dΓ©but des calendriers |
|
384 | setting_start_of_week: Jour de dΓ©but des calendriers | |
385 | setting_cache_formatted_text: Mettre en cache le texte formatΓ© |
|
385 | setting_cache_formatted_text: Mettre en cache le texte formatΓ© | |
386 | setting_commit_logtime_enabled: Permettre la saisie de temps |
|
386 | setting_commit_logtime_enabled: Permettre la saisie de temps | |
387 | setting_commit_logtime_activity_id: ActivitΓ© pour le temps saisi |
|
387 | setting_commit_logtime_activity_id: ActivitΓ© pour le temps saisi | |
388 | setting_gantt_items_limit: Nombre maximum d'Γ©lΓ©ments affichΓ©s sur le gantt |
|
388 | setting_gantt_items_limit: Nombre maximum d'Γ©lΓ©ments affichΓ©s sur le gantt | |
389 | setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes |
|
389 | setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes | |
390 | 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_default_issue_start_date_to_creation_date: Donner Γ la date de dΓ©but d'une nouvelle demande la valeur de la date du jour | |
391 | setting_commit_cross_project_ref: Permettre le rΓ©fΓ©rencement et la rΓ©solution des demandes de tous les autres projets |
|
391 | setting_commit_cross_project_ref: Permettre le rΓ©fΓ©rencement et la rΓ©solution des demandes de tous les autres projets | |
392 | setting_unsubscribe: Permettre aux utilisateurs de supprimer leur propre compte |
|
392 | setting_unsubscribe: Permettre aux utilisateurs de supprimer leur propre compte | |
393 | setting_session_lifetime: DurΓ©e de vie maximale des sessions |
|
393 | setting_session_lifetime: DurΓ©e de vie maximale des sessions | |
394 | setting_session_timeout: DurΓ©e maximale d'inactivitΓ© |
|
394 | setting_session_timeout: DurΓ©e maximale d'inactivitΓ© | |
395 | setting_thumbnails_enabled: Afficher les vignettes des images |
|
395 | setting_thumbnails_enabled: Afficher les vignettes des images | |
396 | setting_thumbnails_size: Taille des vignettes (en pixels) |
|
396 | setting_thumbnails_size: Taille des vignettes (en pixels) | |
397 |
|
397 | |||
398 | permission_add_project: CrΓ©er un projet |
|
398 | permission_add_project: CrΓ©er un projet | |
399 | permission_add_subprojects: CrΓ©er des sous-projets |
|
399 | permission_add_subprojects: CrΓ©er des sous-projets | |
400 | permission_edit_project: Modifier le projet |
|
400 | permission_edit_project: Modifier le projet | |
401 | permission_close_project: Fermer / rΓ©ouvrir le projet |
|
401 | permission_close_project: Fermer / rΓ©ouvrir le projet | |
402 | permission_select_project_modules: Choisir les modules |
|
402 | permission_select_project_modules: Choisir les modules | |
403 | permission_manage_members: GΓ©rer les membres |
|
403 | permission_manage_members: GΓ©rer les membres | |
404 | permission_manage_versions: GΓ©rer les versions |
|
404 | permission_manage_versions: GΓ©rer les versions | |
405 | permission_manage_categories: GΓ©rer les catΓ©gories de demandes |
|
405 | permission_manage_categories: GΓ©rer les catΓ©gories de demandes | |
406 | permission_view_issues: Voir les demandes |
|
406 | permission_view_issues: Voir les demandes | |
407 | permission_add_issues: CrΓ©er des demandes |
|
407 | permission_add_issues: CrΓ©er des demandes | |
408 | permission_edit_issues: Modifier les demandes |
|
408 | permission_edit_issues: Modifier les demandes | |
409 | permission_manage_issue_relations: GΓ©rer les relations |
|
409 | permission_manage_issue_relations: GΓ©rer les relations | |
410 | permission_set_issues_private: Rendre les demandes publiques ou privΓ©es |
|
410 | permission_set_issues_private: Rendre les demandes publiques ou privΓ©es | |
411 | permission_set_own_issues_private: Rendre ses propres demandes publiques ou privΓ©es |
|
411 | permission_set_own_issues_private: Rendre ses propres demandes publiques ou privΓ©es | |
412 | permission_add_issue_notes: Ajouter des notes |
|
412 | permission_add_issue_notes: Ajouter des notes | |
413 | permission_edit_issue_notes: Modifier les notes |
|
413 | permission_edit_issue_notes: Modifier les notes | |
414 | permission_edit_own_issue_notes: Modifier ses propres notes |
|
414 | permission_edit_own_issue_notes: Modifier ses propres notes | |
415 | permission_view_private_notes: Voir les notes privΓ©es |
|
415 | permission_view_private_notes: Voir les notes privΓ©es | |
416 | permission_set_notes_private: Rendre les notes privΓ©es |
|
416 | permission_set_notes_private: Rendre les notes privΓ©es | |
417 | permission_move_issues: DΓ©placer les demandes |
|
417 | permission_move_issues: DΓ©placer les demandes | |
418 | permission_delete_issues: Supprimer les demandes |
|
418 | permission_delete_issues: Supprimer les demandes | |
419 | permission_manage_public_queries: GΓ©rer les requΓͺtes publiques |
|
419 | permission_manage_public_queries: GΓ©rer les requΓͺtes publiques | |
420 | permission_save_queries: Sauvegarder les requΓͺtes |
|
420 | permission_save_queries: Sauvegarder les requΓͺtes | |
421 | permission_view_gantt: Voir le gantt |
|
421 | permission_view_gantt: Voir le gantt | |
422 | permission_view_calendar: Voir le calendrier |
|
422 | permission_view_calendar: Voir le calendrier | |
423 | permission_view_issue_watchers: Voir la liste des observateurs |
|
423 | permission_view_issue_watchers: Voir la liste des observateurs | |
424 | permission_add_issue_watchers: Ajouter des observateurs |
|
424 | permission_add_issue_watchers: Ajouter des observateurs | |
425 | permission_delete_issue_watchers: Supprimer des observateurs |
|
425 | permission_delete_issue_watchers: Supprimer des observateurs | |
426 | permission_log_time: Saisir le temps passΓ© |
|
426 | permission_log_time: Saisir le temps passΓ© | |
427 | permission_view_time_entries: Voir le temps passΓ© |
|
427 | permission_view_time_entries: Voir le temps passΓ© | |
428 | permission_edit_time_entries: Modifier les temps passΓ©s |
|
428 | permission_edit_time_entries: Modifier les temps passΓ©s | |
429 | permission_edit_own_time_entries: Modifier son propre temps passΓ© |
|
429 | permission_edit_own_time_entries: Modifier son propre temps passΓ© | |
430 | permission_manage_news: GΓ©rer les annonces |
|
430 | permission_manage_news: GΓ©rer les annonces | |
431 | permission_comment_news: Commenter les annonces |
|
431 | permission_comment_news: Commenter les annonces | |
432 | permission_manage_documents: GΓ©rer les documents |
|
432 | permission_manage_documents: GΓ©rer les documents | |
433 | permission_view_documents: Voir les documents |
|
433 | permission_view_documents: Voir les documents | |
434 | permission_manage_files: GΓ©rer les fichiers |
|
434 | permission_manage_files: GΓ©rer les fichiers | |
435 | permission_view_files: Voir les fichiers |
|
435 | permission_view_files: Voir les fichiers | |
436 | permission_manage_wiki: GΓ©rer le wiki |
|
436 | permission_manage_wiki: GΓ©rer le wiki | |
437 | permission_rename_wiki_pages: Renommer les pages |
|
437 | permission_rename_wiki_pages: Renommer les pages | |
438 | permission_delete_wiki_pages: Supprimer les pages |
|
438 | permission_delete_wiki_pages: Supprimer les pages | |
439 | permission_view_wiki_pages: Voir le wiki |
|
439 | permission_view_wiki_pages: Voir le wiki | |
440 | permission_view_wiki_edits: "Voir l'historique des modifications" |
|
440 | permission_view_wiki_edits: "Voir l'historique des modifications" | |
441 | permission_edit_wiki_pages: Modifier les pages |
|
441 | permission_edit_wiki_pages: Modifier les pages | |
442 | permission_delete_wiki_pages_attachments: Supprimer les fichiers joints |
|
442 | permission_delete_wiki_pages_attachments: Supprimer les fichiers joints | |
443 | permission_protect_wiki_pages: ProtΓ©ger les pages |
|
443 | permission_protect_wiki_pages: ProtΓ©ger les pages | |
444 | permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources |
|
444 | permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources | |
445 | permission_browse_repository: Parcourir les sources |
|
445 | permission_browse_repository: Parcourir les sources | |
446 | permission_view_changesets: Voir les rΓ©visions |
|
446 | permission_view_changesets: Voir les rΓ©visions | |
447 | permission_commit_access: Droit de commit |
|
447 | permission_commit_access: Droit de commit | |
448 | permission_manage_boards: GΓ©rer les forums |
|
448 | permission_manage_boards: GΓ©rer les forums | |
449 | permission_view_messages: Voir les messages |
|
449 | permission_view_messages: Voir les messages | |
450 | permission_add_messages: Poster un message |
|
450 | permission_add_messages: Poster un message | |
451 | permission_edit_messages: Modifier les messages |
|
451 | permission_edit_messages: Modifier les messages | |
452 | permission_edit_own_messages: Modifier ses propres messages |
|
452 | permission_edit_own_messages: Modifier ses propres messages | |
453 | permission_delete_messages: Supprimer les messages |
|
453 | permission_delete_messages: Supprimer les messages | |
454 | permission_delete_own_messages: Supprimer ses propres messages |
|
454 | permission_delete_own_messages: Supprimer ses propres messages | |
455 | permission_export_wiki_pages: Exporter les pages |
|
455 | permission_export_wiki_pages: Exporter les pages | |
456 | permission_manage_project_activities: GΓ©rer les activitΓ©s |
|
456 | permission_manage_project_activities: GΓ©rer les activitΓ©s | |
457 | permission_manage_subtasks: GΓ©rer les sous-tΓ’ches |
|
457 | permission_manage_subtasks: GΓ©rer les sous-tΓ’ches | |
458 | permission_manage_related_issues: GΓ©rer les demandes associΓ©es |
|
458 | permission_manage_related_issues: GΓ©rer les demandes associΓ©es | |
459 |
|
459 | |||
460 | project_module_issue_tracking: Suivi des demandes |
|
460 | project_module_issue_tracking: Suivi des demandes | |
461 | project_module_time_tracking: Suivi du temps passΓ© |
|
461 | project_module_time_tracking: Suivi du temps passΓ© | |
462 | project_module_news: Publication d'annonces |
|
462 | project_module_news: Publication d'annonces | |
463 | project_module_documents: Publication de documents |
|
463 | project_module_documents: Publication de documents | |
464 | project_module_files: Publication de fichiers |
|
464 | project_module_files: Publication de fichiers | |
465 | project_module_wiki: Wiki |
|
465 | project_module_wiki: Wiki | |
466 | project_module_repository: DΓ©pΓ΄t de sources |
|
466 | project_module_repository: DΓ©pΓ΄t de sources | |
467 | project_module_boards: Forums de discussion |
|
467 | project_module_boards: Forums de discussion | |
468 |
|
468 | |||
469 | label_user: Utilisateur |
|
469 | label_user: Utilisateur | |
470 | label_user_plural: Utilisateurs |
|
470 | label_user_plural: Utilisateurs | |
471 | label_user_new: Nouvel utilisateur |
|
471 | label_user_new: Nouvel utilisateur | |
472 | label_user_anonymous: Anonyme |
|
472 | label_user_anonymous: Anonyme | |
473 | label_project: Projet |
|
473 | label_project: Projet | |
474 | label_project_new: Nouveau projet |
|
474 | label_project_new: Nouveau projet | |
475 | label_project_plural: Projets |
|
475 | label_project_plural: Projets | |
476 | label_x_projects: |
|
476 | label_x_projects: | |
477 | zero: aucun projet |
|
477 | zero: aucun projet | |
478 | one: un projet |
|
478 | one: un projet | |
479 | other: "%{count} projets" |
|
479 | other: "%{count} projets" | |
480 | label_project_all: Tous les projets |
|
480 | label_project_all: Tous les projets | |
481 | label_project_latest: Derniers projets |
|
481 | label_project_latest: Derniers projets | |
482 | label_issue: Demande |
|
482 | label_issue: Demande | |
483 | label_issue_new: Nouvelle demande |
|
483 | label_issue_new: Nouvelle demande | |
484 | label_issue_plural: Demandes |
|
484 | label_issue_plural: Demandes | |
485 | label_issue_view_all: Voir toutes les demandes |
|
485 | label_issue_view_all: Voir toutes les demandes | |
486 | label_issue_added: Demande ajoutΓ©e |
|
486 | label_issue_added: Demande ajoutΓ©e | |
487 | label_issue_updated: Demande mise Γ jour |
|
487 | label_issue_updated: Demande mise Γ jour | |
488 | label_issue_note_added: Note ajoutΓ©e |
|
488 | label_issue_note_added: Note ajoutΓ©e | |
489 | label_issue_status_updated: Statut changΓ© |
|
489 | label_issue_status_updated: Statut changΓ© | |
490 | label_issue_priority_updated: PrioritΓ© changΓ©e |
|
490 | label_issue_priority_updated: PrioritΓ© changΓ©e | |
491 | label_issues_by: "Demandes par %{value}" |
|
491 | label_issues_by: "Demandes par %{value}" | |
492 | label_document: Document |
|
492 | label_document: Document | |
493 | label_document_new: Nouveau document |
|
493 | label_document_new: Nouveau document | |
494 | label_document_plural: Documents |
|
494 | label_document_plural: Documents | |
495 | label_document_added: Document ajoutΓ© |
|
495 | label_document_added: Document ajoutΓ© | |
496 | label_role: RΓ΄le |
|
496 | label_role: RΓ΄le | |
497 | label_role_plural: RΓ΄les |
|
497 | label_role_plural: RΓ΄les | |
498 | label_role_new: Nouveau rΓ΄le |
|
498 | label_role_new: Nouveau rΓ΄le | |
499 | label_role_and_permissions: RΓ΄les et permissions |
|
499 | label_role_and_permissions: RΓ΄les et permissions | |
500 | label_role_anonymous: Anonyme |
|
500 | label_role_anonymous: Anonyme | |
501 | label_role_non_member: Non membre |
|
501 | label_role_non_member: Non membre | |
502 | label_member: Membre |
|
502 | label_member: Membre | |
503 | label_member_new: Nouveau membre |
|
503 | label_member_new: Nouveau membre | |
504 | label_member_plural: Membres |
|
504 | label_member_plural: Membres | |
505 | label_tracker: Tracker |
|
505 | label_tracker: Tracker | |
506 | label_tracker_plural: Trackers |
|
506 | label_tracker_plural: Trackers | |
507 | label_tracker_new: Nouveau tracker |
|
507 | label_tracker_new: Nouveau tracker | |
508 | label_workflow: Workflow |
|
508 | label_workflow: Workflow | |
509 | label_issue_status: Statut de demandes |
|
509 | label_issue_status: Statut de demandes | |
510 | label_issue_status_plural: Statuts de demandes |
|
510 | label_issue_status_plural: Statuts de demandes | |
511 | label_issue_status_new: Nouveau statut |
|
511 | label_issue_status_new: Nouveau statut | |
512 | label_issue_category: CatΓ©gorie de demandes |
|
512 | label_issue_category: CatΓ©gorie de demandes | |
513 | label_issue_category_plural: CatΓ©gories de demandes |
|
513 | label_issue_category_plural: CatΓ©gories de demandes | |
514 | label_issue_category_new: Nouvelle catΓ©gorie |
|
514 | label_issue_category_new: Nouvelle catΓ©gorie | |
515 | label_custom_field: Champ personnalisΓ© |
|
515 | label_custom_field: Champ personnalisΓ© | |
516 | label_custom_field_plural: Champs personnalisΓ©s |
|
516 | label_custom_field_plural: Champs personnalisΓ©s | |
517 | label_custom_field_new: Nouveau champ personnalisΓ© |
|
517 | label_custom_field_new: Nouveau champ personnalisΓ© | |
518 | label_enumerations: Listes de valeurs |
|
518 | label_enumerations: Listes de valeurs | |
519 | label_enumeration_new: Nouvelle valeur |
|
519 | label_enumeration_new: Nouvelle valeur | |
520 | label_information: Information |
|
520 | label_information: Information | |
521 | label_information_plural: Informations |
|
521 | label_information_plural: Informations | |
522 | label_please_login: Identification |
|
522 | label_please_login: Identification | |
523 | label_register: S'enregistrer |
|
523 | label_register: S'enregistrer | |
524 | label_login_with_open_id_option: S'authentifier avec OpenID |
|
524 | label_login_with_open_id_option: S'authentifier avec OpenID | |
525 | label_password_lost: Mot de passe perdu |
|
525 | label_password_lost: Mot de passe perdu | |
526 | label_home: Accueil |
|
526 | label_home: Accueil | |
527 | label_my_page: Ma page |
|
527 | label_my_page: Ma page | |
528 | label_my_account: Mon compte |
|
528 | label_my_account: Mon compte | |
529 | label_my_projects: Mes projets |
|
529 | label_my_projects: Mes projets | |
530 | label_my_page_block: Blocs disponibles |
|
530 | label_my_page_block: Blocs disponibles | |
531 | label_administration: Administration |
|
531 | label_administration: Administration | |
532 | label_login: Connexion |
|
532 | label_login: Connexion | |
533 | label_logout: DΓ©connexion |
|
533 | label_logout: DΓ©connexion | |
534 | label_help: Aide |
|
534 | label_help: Aide | |
535 | label_reported_issues: "Demandes soumises " |
|
535 | label_reported_issues: "Demandes soumises " | |
536 | label_assigned_to_me_issues: Demandes qui me sont assignΓ©es |
|
536 | label_assigned_to_me_issues: Demandes qui me sont assignΓ©es | |
537 | label_last_login: "Dernière connexion " |
|
537 | label_last_login: "Dernière connexion " | |
538 | label_registered_on: "Inscrit le " |
|
538 | label_registered_on: "Inscrit le " | |
539 | label_activity: ActivitΓ© |
|
539 | label_activity: ActivitΓ© | |
540 | label_overall_activity: ActivitΓ© globale |
|
540 | label_overall_activity: ActivitΓ© globale | |
541 | label_user_activity: "ActivitΓ© de %{value}" |
|
541 | label_user_activity: "ActivitΓ© de %{value}" | |
542 | label_new: Nouveau |
|
542 | label_new: Nouveau | |
543 | label_logged_as: ConnectΓ© en tant que |
|
543 | label_logged_as: ConnectΓ© en tant que | |
544 | label_environment: Environnement |
|
544 | label_environment: Environnement | |
545 | label_authentication: Authentification |
|
545 | label_authentication: Authentification | |
546 | label_auth_source: Mode d'authentification |
|
546 | label_auth_source: Mode d'authentification | |
547 | label_auth_source_new: Nouveau mode d'authentification |
|
547 | label_auth_source_new: Nouveau mode d'authentification | |
548 | label_auth_source_plural: Modes d'authentification |
|
548 | label_auth_source_plural: Modes d'authentification | |
549 | label_subproject_plural: Sous-projets |
|
549 | label_subproject_plural: Sous-projets | |
550 | label_subproject_new: Nouveau sous-projet |
|
550 | label_subproject_new: Nouveau sous-projet | |
551 | label_and_its_subprojects: "%{value} et ses sous-projets" |
|
551 | label_and_its_subprojects: "%{value} et ses sous-projets" | |
552 | label_min_max_length: Longueurs mini - maxi |
|
552 | label_min_max_length: Longueurs mini - maxi | |
553 | label_list: Liste |
|
553 | label_list: Liste | |
554 | label_date: Date |
|
554 | label_date: Date | |
555 | label_integer: Entier |
|
555 | label_integer: Entier | |
556 | label_float: Nombre dΓ©cimal |
|
556 | label_float: Nombre dΓ©cimal | |
557 | label_boolean: BoolΓ©en |
|
557 | label_boolean: BoolΓ©en | |
558 | label_string: Texte |
|
558 | label_string: Texte | |
559 | label_text: Texte long |
|
559 | label_text: Texte long | |
560 | label_attribute: Attribut |
|
560 | label_attribute: Attribut | |
561 | label_attribute_plural: Attributs |
|
561 | label_attribute_plural: Attributs | |
562 | label_download: "%{count} tΓ©lΓ©chargement" |
|
562 | label_download: "%{count} tΓ©lΓ©chargement" | |
563 | label_download_plural: "%{count} tΓ©lΓ©chargements" |
|
563 | label_download_plural: "%{count} tΓ©lΓ©chargements" | |
564 | label_no_data: Aucune donnΓ©e Γ afficher |
|
564 | label_no_data: Aucune donnΓ©e Γ afficher | |
565 | label_change_status: Changer le statut |
|
565 | label_change_status: Changer le statut | |
566 | label_history: Historique |
|
566 | label_history: Historique | |
567 | label_attachment: Fichier |
|
567 | label_attachment: Fichier | |
568 | label_attachment_new: Nouveau fichier |
|
568 | label_attachment_new: Nouveau fichier | |
569 | label_attachment_delete: Supprimer le fichier |
|
569 | label_attachment_delete: Supprimer le fichier | |
570 | label_attachment_plural: Fichiers |
|
570 | label_attachment_plural: Fichiers | |
571 | label_file_added: Fichier ajoutΓ© |
|
571 | label_file_added: Fichier ajoutΓ© | |
572 | label_report: Rapport |
|
572 | label_report: Rapport | |
573 | label_report_plural: Rapports |
|
573 | label_report_plural: Rapports | |
574 | label_news: Annonce |
|
574 | label_news: Annonce | |
575 | label_news_new: Nouvelle annonce |
|
575 | label_news_new: Nouvelle annonce | |
576 | label_news_plural: Annonces |
|
576 | label_news_plural: Annonces | |
577 | label_news_latest: Dernières annonces |
|
577 | label_news_latest: Dernières annonces | |
578 | label_news_view_all: Voir toutes les annonces |
|
578 | label_news_view_all: Voir toutes les annonces | |
579 | label_news_added: Annonce ajoutΓ©e |
|
579 | label_news_added: Annonce ajoutΓ©e | |
580 | label_news_comment_added: Commentaire ajoutΓ© Γ une annonce |
|
580 | label_news_comment_added: Commentaire ajoutΓ© Γ une annonce | |
581 | label_settings: Configuration |
|
581 | label_settings: Configuration | |
582 | label_overview: AperΓ§u |
|
582 | label_overview: AperΓ§u | |
583 | label_version: Version |
|
583 | label_version: Version | |
584 | label_version_new: Nouvelle version |
|
584 | label_version_new: Nouvelle version | |
585 | label_version_plural: Versions |
|
585 | label_version_plural: Versions | |
586 | label_confirmation: Confirmation |
|
586 | label_confirmation: Confirmation | |
587 | label_export_to: 'Formats disponibles :' |
|
587 | label_export_to: 'Formats disponibles :' | |
588 | label_read: Lire... |
|
588 | label_read: Lire... | |
589 | label_public_projects: Projets publics |
|
589 | label_public_projects: Projets publics | |
590 | label_open_issues: ouvert |
|
590 | label_open_issues: ouvert | |
591 | label_open_issues_plural: ouverts |
|
591 | label_open_issues_plural: ouverts | |
592 | label_closed_issues: fermΓ© |
|
592 | label_closed_issues: fermΓ© | |
593 | label_closed_issues_plural: fermΓ©s |
|
593 | label_closed_issues_plural: fermΓ©s | |
594 | label_x_open_issues_abbr_on_total: |
|
594 | label_x_open_issues_abbr_on_total: | |
595 | zero: 0 ouverte sur %{total} |
|
595 | zero: 0 ouverte sur %{total} | |
596 | one: 1 ouverte sur %{total} |
|
596 | one: 1 ouverte sur %{total} | |
597 | other: "%{count} ouvertes sur %{total}" |
|
597 | other: "%{count} ouvertes sur %{total}" | |
598 | label_x_open_issues_abbr: |
|
598 | label_x_open_issues_abbr: | |
599 | zero: 0 ouverte |
|
599 | zero: 0 ouverte | |
600 | one: 1 ouverte |
|
600 | one: 1 ouverte | |
601 | other: "%{count} ouvertes" |
|
601 | other: "%{count} ouvertes" | |
602 | label_x_closed_issues_abbr: |
|
602 | label_x_closed_issues_abbr: | |
603 | zero: 0 fermΓ©e |
|
603 | zero: 0 fermΓ©e | |
604 | one: 1 fermΓ©e |
|
604 | one: 1 fermΓ©e | |
605 | other: "%{count} fermΓ©es" |
|
605 | other: "%{count} fermΓ©es" | |
606 | label_x_issues: |
|
606 | label_x_issues: | |
607 | zero: 0 demande |
|
607 | zero: 0 demande | |
608 | one: 1 demande |
|
608 | one: 1 demande | |
609 | other: "%{count} demandes" |
|
609 | other: "%{count} demandes" | |
610 | label_total: Total |
|
610 | label_total: Total | |
611 | label_permissions: Permissions |
|
611 | label_permissions: Permissions | |
612 | label_current_status: Statut actuel |
|
612 | label_current_status: Statut actuel | |
613 | label_new_statuses_allowed: Nouveaux statuts autorisΓ©s |
|
613 | label_new_statuses_allowed: Nouveaux statuts autorisΓ©s | |
614 | label_all: tous |
|
614 | label_all: tous | |
615 | label_none: aucun |
|
615 | label_none: aucun | |
616 | label_nobody: personne |
|
616 | label_nobody: personne | |
617 | label_next: Suivant |
|
617 | label_next: Suivant | |
618 | label_previous: PrΓ©cΓ©dent |
|
618 | label_previous: PrΓ©cΓ©dent | |
619 | label_used_by: UtilisΓ© par |
|
619 | label_used_by: UtilisΓ© par | |
620 | label_details: DΓ©tails |
|
620 | label_details: DΓ©tails | |
621 | label_add_note: Ajouter une note |
|
621 | label_add_note: Ajouter une note | |
622 | label_per_page: Par page |
|
622 | label_per_page: Par page | |
623 | label_calendar: Calendrier |
|
623 | label_calendar: Calendrier | |
624 | label_months_from: mois depuis |
|
624 | label_months_from: mois depuis | |
625 | label_gantt: Gantt |
|
625 | label_gantt: Gantt | |
626 | label_internal: Interne |
|
626 | label_internal: Interne | |
627 | label_last_changes: "%{count} derniers changements" |
|
627 | label_last_changes: "%{count} derniers changements" | |
628 | label_change_view_all: Voir tous les changements |
|
628 | label_change_view_all: Voir tous les changements | |
629 | label_personalize_page: Personnaliser cette page |
|
629 | label_personalize_page: Personnaliser cette page | |
630 | label_comment: Commentaire |
|
630 | label_comment: Commentaire | |
631 | label_comment_plural: Commentaires |
|
631 | label_comment_plural: Commentaires | |
632 | label_x_comments: |
|
632 | label_x_comments: | |
633 | zero: aucun commentaire |
|
633 | zero: aucun commentaire | |
634 | one: un commentaire |
|
634 | one: un commentaire | |
635 | other: "%{count} commentaires" |
|
635 | other: "%{count} commentaires" | |
636 | label_comment_add: Ajouter un commentaire |
|
636 | label_comment_add: Ajouter un commentaire | |
637 | label_comment_added: Commentaire ajoutΓ© |
|
637 | label_comment_added: Commentaire ajoutΓ© | |
638 | label_comment_delete: Supprimer les commentaires |
|
638 | label_comment_delete: Supprimer les commentaires | |
639 | label_query: Rapport personnalisΓ© |
|
639 | label_query: Rapport personnalisΓ© | |
640 | label_query_plural: Rapports personnalisΓ©s |
|
640 | label_query_plural: Rapports personnalisΓ©s | |
641 | label_query_new: Nouveau rapport |
|
641 | label_query_new: Nouveau rapport | |
642 | label_my_queries: Mes rapports personnalisΓ©s |
|
642 | label_my_queries: Mes rapports personnalisΓ©s | |
643 | label_filter_add: "Ajouter le filtre " |
|
643 | label_filter_add: "Ajouter le filtre " | |
644 | label_filter_plural: Filtres |
|
644 | label_filter_plural: Filtres | |
645 | label_equals: Γ©gal |
|
645 | label_equals: Γ©gal | |
646 | label_not_equals: diffΓ©rent |
|
646 | label_not_equals: diffΓ©rent | |
647 | label_in_less_than: dans moins de |
|
647 | label_in_less_than: dans moins de | |
648 | label_in_more_than: dans plus de |
|
648 | label_in_more_than: dans plus de | |
649 | label_in: dans |
|
649 | label_in: dans | |
650 | label_today: aujourd'hui |
|
650 | label_today: aujourd'hui | |
651 | label_all_time: toute la pΓ©riode |
|
651 | label_all_time: toute la pΓ©riode | |
652 | label_yesterday: hier |
|
652 | label_yesterday: hier | |
653 | label_this_week: cette semaine |
|
653 | label_this_week: cette semaine | |
654 | label_last_week: la semaine dernière |
|
654 | label_last_week: la semaine dernière | |
655 | label_last_n_days: "les %{count} derniers jours" |
|
655 | label_last_n_days: "les %{count} derniers jours" | |
656 | label_this_month: ce mois-ci |
|
656 | label_this_month: ce mois-ci | |
657 | label_last_month: le mois dernier |
|
657 | label_last_month: le mois dernier | |
658 | label_this_year: cette annΓ©e |
|
658 | label_this_year: cette annΓ©e | |
659 | label_date_range: PΓ©riode |
|
659 | label_date_range: PΓ©riode | |
660 | label_less_than_ago: il y a moins de |
|
660 | label_less_than_ago: il y a moins de | |
661 | label_more_than_ago: il y a plus de |
|
661 | label_more_than_ago: il y a plus de | |
662 | label_ago: il y a |
|
662 | label_ago: il y a | |
663 | label_contains: contient |
|
663 | label_contains: contient | |
664 | label_not_contains: ne contient pas |
|
664 | label_not_contains: ne contient pas | |
665 | label_any_issues_in_project: une demande du projet |
|
665 | label_any_issues_in_project: une demande du projet | |
666 | label_any_issues_not_in_project: une demande hors du projet |
|
666 | label_any_issues_not_in_project: une demande hors du projet | |
|
667 | label_no_issues_in_project: aucune demande du projet | |||
667 | label_day_plural: jours |
|
668 | label_day_plural: jours | |
668 | label_repository: DΓ©pΓ΄t |
|
669 | label_repository: DΓ©pΓ΄t | |
669 | label_repository_new: Nouveau dΓ©pΓ΄t |
|
670 | label_repository_new: Nouveau dΓ©pΓ΄t | |
670 | label_repository_plural: DΓ©pΓ΄ts |
|
671 | label_repository_plural: DΓ©pΓ΄ts | |
671 | label_browse: Parcourir |
|
672 | label_browse: Parcourir | |
672 | label_modification: "%{count} modification" |
|
673 | label_modification: "%{count} modification" | |
673 | label_modification_plural: "%{count} modifications" |
|
674 | label_modification_plural: "%{count} modifications" | |
674 | label_revision: "RΓ©vision " |
|
675 | label_revision: "RΓ©vision " | |
675 | label_revision_plural: RΓ©visions |
|
676 | label_revision_plural: RΓ©visions | |
676 | label_associated_revisions: RΓ©visions associΓ©es |
|
677 | label_associated_revisions: RΓ©visions associΓ©es | |
677 | label_added: ajoutΓ© |
|
678 | label_added: ajoutΓ© | |
678 | label_modified: modifiΓ© |
|
679 | label_modified: modifiΓ© | |
679 | label_copied: copiΓ© |
|
680 | label_copied: copiΓ© | |
680 | label_renamed: renommΓ© |
|
681 | label_renamed: renommΓ© | |
681 | label_deleted: supprimΓ© |
|
682 | label_deleted: supprimΓ© | |
682 | label_latest_revision: Dernière révision |
|
683 | label_latest_revision: Dernière révision | |
683 | label_latest_revision_plural: Dernières révisions |
|
684 | label_latest_revision_plural: Dernières révisions | |
684 | label_view_revisions: Voir les rΓ©visions |
|
685 | label_view_revisions: Voir les rΓ©visions | |
685 | label_max_size: Taille maximale |
|
686 | label_max_size: Taille maximale | |
686 | label_sort_highest: Remonter en premier |
|
687 | label_sort_highest: Remonter en premier | |
687 | label_sort_higher: Remonter |
|
688 | label_sort_higher: Remonter | |
688 | label_sort_lower: Descendre |
|
689 | label_sort_lower: Descendre | |
689 | label_sort_lowest: Descendre en dernier |
|
690 | label_sort_lowest: Descendre en dernier | |
690 | label_roadmap: Roadmap |
|
691 | label_roadmap: Roadmap | |
691 | label_roadmap_due_in: "ΓchΓ©ance dans %{value}" |
|
692 | label_roadmap_due_in: "ΓchΓ©ance dans %{value}" | |
692 | label_roadmap_overdue: "En retard de %{value}" |
|
693 | label_roadmap_overdue: "En retard de %{value}" | |
693 | label_roadmap_no_issues: Aucune demande pour cette version |
|
694 | label_roadmap_no_issues: Aucune demande pour cette version | |
694 | label_search: "Recherche " |
|
695 | label_search: "Recherche " | |
695 | label_result_plural: RΓ©sultats |
|
696 | label_result_plural: RΓ©sultats | |
696 | label_all_words: Tous les mots |
|
697 | label_all_words: Tous les mots | |
697 | label_wiki: Wiki |
|
698 | label_wiki: Wiki | |
698 | label_wiki_edit: RΓ©vision wiki |
|
699 | label_wiki_edit: RΓ©vision wiki | |
699 | label_wiki_edit_plural: RΓ©visions wiki |
|
700 | label_wiki_edit_plural: RΓ©visions wiki | |
700 | label_wiki_page: Page wiki |
|
701 | label_wiki_page: Page wiki | |
701 | label_wiki_page_plural: Pages wiki |
|
702 | label_wiki_page_plural: Pages wiki | |
702 | label_index_by_title: Index par titre |
|
703 | label_index_by_title: Index par titre | |
703 | label_index_by_date: Index par date |
|
704 | label_index_by_date: Index par date | |
704 | label_current_version: Version actuelle |
|
705 | label_current_version: Version actuelle | |
705 | label_preview: PrΓ©visualisation |
|
706 | label_preview: PrΓ©visualisation | |
706 | label_feed_plural: Flux RSS |
|
707 | label_feed_plural: Flux RSS | |
707 | label_changes_details: DΓ©tails de tous les changements |
|
708 | label_changes_details: DΓ©tails de tous les changements | |
708 | label_issue_tracking: Suivi des demandes |
|
709 | label_issue_tracking: Suivi des demandes | |
709 | label_spent_time: Temps passΓ© |
|
710 | label_spent_time: Temps passΓ© | |
710 | label_f_hour: "%{value} heure" |
|
711 | label_f_hour: "%{value} heure" | |
711 | label_f_hour_plural: "%{value} heures" |
|
712 | label_f_hour_plural: "%{value} heures" | |
712 | label_time_tracking: Suivi du temps |
|
713 | label_time_tracking: Suivi du temps | |
713 | label_change_plural: Changements |
|
714 | label_change_plural: Changements | |
714 | label_statistics: Statistiques |
|
715 | label_statistics: Statistiques | |
715 | label_commits_per_month: Commits par mois |
|
716 | label_commits_per_month: Commits par mois | |
716 | label_commits_per_author: Commits par auteur |
|
717 | label_commits_per_author: Commits par auteur | |
717 | label_view_diff: Voir les diffΓ©rences |
|
718 | label_view_diff: Voir les diffΓ©rences | |
718 | label_diff_inline: en ligne |
|
719 | label_diff_inline: en ligne | |
719 | label_diff_side_by_side: cΓ΄te Γ cΓ΄te |
|
720 | label_diff_side_by_side: cΓ΄te Γ cΓ΄te | |
720 | label_options: Options |
|
721 | label_options: Options | |
721 | label_copy_workflow_from: Copier le workflow de |
|
722 | label_copy_workflow_from: Copier le workflow de | |
722 | label_permissions_report: Synthèse des permissions |
|
723 | label_permissions_report: Synthèse des permissions | |
723 | label_watched_issues: Demandes surveillΓ©es |
|
724 | label_watched_issues: Demandes surveillΓ©es | |
724 | label_related_issues: Demandes liΓ©es |
|
725 | label_related_issues: Demandes liΓ©es | |
725 | label_applied_status: Statut appliquΓ© |
|
726 | label_applied_status: Statut appliquΓ© | |
726 | label_loading: Chargement... |
|
727 | label_loading: Chargement... | |
727 | label_relation_new: Nouvelle relation |
|
728 | label_relation_new: Nouvelle relation | |
728 | label_relation_delete: Supprimer la relation |
|
729 | label_relation_delete: Supprimer la relation | |
729 | label_relates_to: LiΓ© Γ |
|
730 | label_relates_to: LiΓ© Γ | |
730 | label_duplicates: Duplique |
|
731 | label_duplicates: Duplique | |
731 | label_duplicated_by: DupliquΓ© par |
|
732 | label_duplicated_by: DupliquΓ© par | |
732 | label_blocks: Bloque |
|
733 | label_blocks: Bloque | |
733 | label_blocked_by: BloquΓ© par |
|
734 | label_blocked_by: BloquΓ© par | |
734 | label_precedes: Précède |
|
735 | label_precedes: Précède | |
735 | label_follows: Suit |
|
736 | label_follows: Suit | |
736 | label_copied_to: CopiΓ© vers |
|
737 | label_copied_to: CopiΓ© vers | |
737 | label_copied_from: CopiΓ© depuis |
|
738 | label_copied_from: CopiΓ© depuis | |
738 | label_end_to_start: fin Γ dΓ©but |
|
739 | label_end_to_start: fin Γ dΓ©but | |
739 | label_end_to_end: fin Γ fin |
|
740 | label_end_to_end: fin Γ fin | |
740 | label_start_to_start: dΓ©but Γ dΓ©but |
|
741 | label_start_to_start: dΓ©but Γ dΓ©but | |
741 | label_start_to_end: dΓ©but Γ fin |
|
742 | label_start_to_end: dΓ©but Γ fin | |
742 | label_stay_logged_in: Rester connectΓ© |
|
743 | label_stay_logged_in: Rester connectΓ© | |
743 | label_disabled: dΓ©sactivΓ© |
|
744 | label_disabled: dΓ©sactivΓ© | |
744 | label_show_completed_versions: Voir les versions passΓ©es |
|
745 | label_show_completed_versions: Voir les versions passΓ©es | |
745 | label_me: moi |
|
746 | label_me: moi | |
746 | label_board: Forum |
|
747 | label_board: Forum | |
747 | label_board_new: Nouveau forum |
|
748 | label_board_new: Nouveau forum | |
748 | label_board_plural: Forums |
|
749 | label_board_plural: Forums | |
749 | label_topic_plural: Discussions |
|
750 | label_topic_plural: Discussions | |
750 | label_message_plural: Messages |
|
751 | label_message_plural: Messages | |
751 | label_message_last: Dernier message |
|
752 | label_message_last: Dernier message | |
752 | label_message_new: Nouveau message |
|
753 | label_message_new: Nouveau message | |
753 | label_message_posted: Message ajoutΓ© |
|
754 | label_message_posted: Message ajoutΓ© | |
754 | label_reply_plural: RΓ©ponses |
|
755 | label_reply_plural: RΓ©ponses | |
755 | label_send_information: Envoyer les informations Γ l'utilisateur |
|
756 | label_send_information: Envoyer les informations Γ l'utilisateur | |
756 | label_year: AnnΓ©e |
|
757 | label_year: AnnΓ©e | |
757 | label_month: Mois |
|
758 | label_month: Mois | |
758 | label_week: Semaine |
|
759 | label_week: Semaine | |
759 | label_date_from: Du |
|
760 | label_date_from: Du | |
760 | label_date_to: Au |
|
761 | label_date_to: Au | |
761 | label_language_based: BasΓ© sur la langue de l'utilisateur |
|
762 | label_language_based: BasΓ© sur la langue de l'utilisateur | |
762 | label_sort_by: "Trier par %{value}" |
|
763 | label_sort_by: "Trier par %{value}" | |
763 | label_send_test_email: Envoyer un email de test |
|
764 | label_send_test_email: Envoyer un email de test | |
764 | label_feeds_access_key_created_on: "Clé d'accès RSS créée il y a %{value}" |
|
765 | label_feeds_access_key_created_on: "Clé d'accès RSS créée il y a %{value}" | |
765 | label_module_plural: Modules |
|
766 | label_module_plural: Modules | |
766 | label_added_time_by: "AjoutΓ© par %{author} il y a %{age}" |
|
767 | label_added_time_by: "AjoutΓ© par %{author} il y a %{age}" | |
767 | label_updated_time_by: "Mis Γ jour par %{author} il y a %{age}" |
|
768 | label_updated_time_by: "Mis Γ jour par %{author} il y a %{age}" | |
768 | label_updated_time: "Mis Γ jour il y a %{value}" |
|
769 | label_updated_time: "Mis Γ jour il y a %{value}" | |
769 | label_jump_to_a_project: Aller Γ un projet... |
|
770 | label_jump_to_a_project: Aller Γ un projet... | |
770 | label_file_plural: Fichiers |
|
771 | label_file_plural: Fichiers | |
771 | label_changeset_plural: RΓ©visions |
|
772 | label_changeset_plural: RΓ©visions | |
772 | label_default_columns: Colonnes par dΓ©faut |
|
773 | label_default_columns: Colonnes par dΓ©faut | |
773 | label_no_change_option: (Pas de changement) |
|
774 | label_no_change_option: (Pas de changement) | |
774 | label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es |
|
775 | label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es | |
775 | label_theme: Thème |
|
776 | label_theme: Thème | |
776 | label_default: DΓ©faut |
|
777 | label_default: DΓ©faut | |
777 | label_search_titles_only: Uniquement dans les titres |
|
778 | label_search_titles_only: Uniquement dans les titres | |
778 | label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets" |
|
779 | label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets" | |
779 | label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..." |
|
780 | label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..." | |
780 | label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue" |
|
781 | label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue" | |
781 | label_registration_activation_by_email: activation du compte par email |
|
782 | label_registration_activation_by_email: activation du compte par email | |
782 | label_registration_manual_activation: activation manuelle du compte |
|
783 | label_registration_manual_activation: activation manuelle du compte | |
783 | label_registration_automatic_activation: activation automatique du compte |
|
784 | label_registration_automatic_activation: activation automatique du compte | |
784 | label_display_per_page: "Par page : %{value}" |
|
785 | label_display_per_page: "Par page : %{value}" | |
785 | label_age: Γge |
|
786 | label_age: Γge | |
786 | label_change_properties: Changer les propriΓ©tΓ©s |
|
787 | label_change_properties: Changer les propriΓ©tΓ©s | |
787 | label_general: GΓ©nΓ©ral |
|
788 | label_general: GΓ©nΓ©ral | |
788 | label_more: Plus |
|
789 | label_more: Plus | |
789 | label_scm: SCM |
|
790 | label_scm: SCM | |
790 | label_plugins: Plugins |
|
791 | label_plugins: Plugins | |
791 | label_ldap_authentication: Authentification LDAP |
|
792 | label_ldap_authentication: Authentification LDAP | |
792 | label_downloads_abbr: D/L |
|
793 | label_downloads_abbr: D/L | |
793 | label_optional_description: Description facultative |
|
794 | label_optional_description: Description facultative | |
794 | label_add_another_file: Ajouter un autre fichier |
|
795 | label_add_another_file: Ajouter un autre fichier | |
795 | label_preferences: PrΓ©fΓ©rences |
|
796 | label_preferences: PrΓ©fΓ©rences | |
796 | label_chronological_order: Dans l'ordre chronologique |
|
797 | label_chronological_order: Dans l'ordre chronologique | |
797 | label_reverse_chronological_order: Dans l'ordre chronologique inverse |
|
798 | label_reverse_chronological_order: Dans l'ordre chronologique inverse | |
798 | label_planning: Planning |
|
799 | label_planning: Planning | |
799 | label_incoming_emails: Emails entrants |
|
800 | label_incoming_emails: Emails entrants | |
800 | label_generate_key: GΓ©nΓ©rer une clΓ© |
|
801 | label_generate_key: GΓ©nΓ©rer une clΓ© | |
801 | label_issue_watchers: Observateurs |
|
802 | label_issue_watchers: Observateurs | |
802 | label_example: Exemple |
|
803 | label_example: Exemple | |
803 | label_display: Affichage |
|
804 | label_display: Affichage | |
804 | label_sort: Tri |
|
805 | label_sort: Tri | |
805 | label_ascending: Croissant |
|
806 | label_ascending: Croissant | |
806 | label_descending: DΓ©croissant |
|
807 | label_descending: DΓ©croissant | |
807 | label_date_from_to: Du %{start} au %{end} |
|
808 | label_date_from_to: Du %{start} au %{end} | |
808 | label_wiki_content_added: Page wiki ajoutΓ©e |
|
809 | label_wiki_content_added: Page wiki ajoutΓ©e | |
809 | label_wiki_content_updated: Page wiki mise Γ jour |
|
810 | label_wiki_content_updated: Page wiki mise Γ jour | |
810 | label_group_plural: Groupes |
|
811 | label_group_plural: Groupes | |
811 | label_group: Groupe |
|
812 | label_group: Groupe | |
812 | label_group_new: Nouveau groupe |
|
813 | label_group_new: Nouveau groupe | |
813 | label_time_entry_plural: Temps passΓ© |
|
814 | label_time_entry_plural: Temps passΓ© | |
814 | label_version_sharing_none: Non partagΓ© |
|
815 | label_version_sharing_none: Non partagΓ© | |
815 | label_version_sharing_descendants: Avec les sous-projets |
|
816 | label_version_sharing_descendants: Avec les sous-projets | |
816 | label_version_sharing_hierarchy: Avec toute la hiΓ©rarchie |
|
817 | label_version_sharing_hierarchy: Avec toute la hiΓ©rarchie | |
817 | label_version_sharing_tree: Avec tout l'arbre |
|
818 | label_version_sharing_tree: Avec tout l'arbre | |
818 | label_version_sharing_system: Avec tous les projets |
|
819 | label_version_sharing_system: Avec tous les projets | |
819 | label_copy_source: Source |
|
820 | label_copy_source: Source | |
820 | label_copy_target: Cible |
|
821 | label_copy_target: Cible | |
821 | label_copy_same_as_target: Comme la cible |
|
822 | label_copy_same_as_target: Comme la cible | |
822 | label_update_issue_done_ratios: Mettre Γ jour l'avancement des demandes |
|
823 | label_update_issue_done_ratios: Mettre Γ jour l'avancement des demandes | |
823 | label_display_used_statuses_only: N'afficher que les statuts utilisΓ©s dans ce tracker |
|
824 | label_display_used_statuses_only: N'afficher que les statuts utilisΓ©s dans ce tracker | |
824 | label_api_access_key: Clé d'accès API |
|
825 | label_api_access_key: Clé d'accès API | |
825 | label_api_access_key_created_on: Clé d'accès API créée il y a %{value} |
|
826 | label_api_access_key_created_on: Clé d'accès API créée il y a %{value} | |
826 | label_feeds_access_key: Clé d'accès RSS |
|
827 | label_feeds_access_key: Clé d'accès RSS | |
827 | label_missing_api_access_key: Clé d'accès API manquante |
|
828 | label_missing_api_access_key: Clé d'accès API manquante | |
828 | label_missing_feeds_access_key: Clé d'accès RSS manquante |
|
829 | label_missing_feeds_access_key: Clé d'accès RSS manquante | |
829 | label_close_versions: Fermer les versions terminΓ©es |
|
830 | label_close_versions: Fermer les versions terminΓ©es | |
830 | label_revision_id: RΓ©vision %{value} |
|
831 | label_revision_id: RΓ©vision %{value} | |
831 | label_profile: Profil |
|
832 | label_profile: Profil | |
832 | label_subtask_plural: Sous-tΓ’ches |
|
833 | label_subtask_plural: Sous-tΓ’ches | |
833 | label_project_copy_notifications: Envoyer les notifications durant la copie du projet |
|
834 | label_project_copy_notifications: Envoyer les notifications durant la copie du projet | |
834 | label_principal_search: "Rechercher un utilisateur ou un groupe :" |
|
835 | label_principal_search: "Rechercher un utilisateur ou un groupe :" | |
835 | label_user_search: "Rechercher un utilisateur :" |
|
836 | label_user_search: "Rechercher un utilisateur :" | |
836 | label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande |
|
837 | label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande | |
837 | label_additional_workflow_transitions_for_assignee: Autorisations supplΓ©mentaires lorsque la demande est assignΓ©e Γ l'utilisateur |
|
838 | label_additional_workflow_transitions_for_assignee: Autorisations supplΓ©mentaires lorsque la demande est assignΓ©e Γ l'utilisateur | |
838 | label_issues_visibility_all: Toutes les demandes |
|
839 | label_issues_visibility_all: Toutes les demandes | |
839 | label_issues_visibility_public: Toutes les demandes non privΓ©es |
|
840 | label_issues_visibility_public: Toutes les demandes non privΓ©es | |
840 | label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur |
|
841 | label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur | |
841 | label_export_options: Options d'exportation %{export_format} |
|
842 | label_export_options: Options d'exportation %{export_format} | |
842 | label_copy_attachments: Copier les fichiers |
|
843 | label_copy_attachments: Copier les fichiers | |
843 | label_copy_subtasks: Copier les sous-tΓ’ches |
|
844 | label_copy_subtasks: Copier les sous-tΓ’ches | |
844 | label_item_position: "%{position} sur %{count}" |
|
845 | label_item_position: "%{position} sur %{count}" | |
845 | label_completed_versions: Versions passΓ©es |
|
846 | label_completed_versions: Versions passΓ©es | |
846 | label_session_expiration: Expiration des sessions |
|
847 | label_session_expiration: Expiration des sessions | |
847 | label_show_closed_projects: Voir les projets fermΓ©s |
|
848 | label_show_closed_projects: Voir les projets fermΓ©s | |
848 | label_status_transitions: Changements de statut |
|
849 | label_status_transitions: Changements de statut | |
849 | label_fields_permissions: Permissions sur les champs |
|
850 | label_fields_permissions: Permissions sur les champs | |
850 | label_readonly: Lecture |
|
851 | label_readonly: Lecture | |
851 | label_required: Obligatoire |
|
852 | label_required: Obligatoire | |
852 | label_attribute_of_project: "%{name} du projet" |
|
853 | label_attribute_of_project: "%{name} du projet" | |
853 | label_attribute_of_author: "%{name} de l'auteur" |
|
854 | label_attribute_of_author: "%{name} de l'auteur" | |
854 | label_attribute_of_assigned_to: "%{name} de l'assignΓ©" |
|
855 | label_attribute_of_assigned_to: "%{name} de l'assignΓ©" | |
855 | label_attribute_of_fixed_version: "%{name} de la version cible" |
|
856 | label_attribute_of_fixed_version: "%{name} de la version cible" | |
856 |
|
857 | |||
857 | button_login: Connexion |
|
858 | button_login: Connexion | |
858 | button_submit: Soumettre |
|
859 | button_submit: Soumettre | |
859 | button_save: Sauvegarder |
|
860 | button_save: Sauvegarder | |
860 | button_check_all: Tout cocher |
|
861 | button_check_all: Tout cocher | |
861 | button_uncheck_all: Tout dΓ©cocher |
|
862 | button_uncheck_all: Tout dΓ©cocher | |
862 | button_collapse_all: Plier tout |
|
863 | button_collapse_all: Plier tout | |
863 | button_expand_all: DΓ©plier tout |
|
864 | button_expand_all: DΓ©plier tout | |
864 | button_delete: Supprimer |
|
865 | button_delete: Supprimer | |
865 | button_create: CrΓ©er |
|
866 | button_create: CrΓ©er | |
866 | button_create_and_continue: CrΓ©er et continuer |
|
867 | button_create_and_continue: CrΓ©er et continuer | |
867 | button_test: Tester |
|
868 | button_test: Tester | |
868 | button_edit: Modifier |
|
869 | button_edit: Modifier | |
869 | button_add: Ajouter |
|
870 | button_add: Ajouter | |
870 | button_change: Changer |
|
871 | button_change: Changer | |
871 | button_apply: Appliquer |
|
872 | button_apply: Appliquer | |
872 | button_clear: Effacer |
|
873 | button_clear: Effacer | |
873 | button_lock: Verrouiller |
|
874 | button_lock: Verrouiller | |
874 | button_unlock: DΓ©verrouiller |
|
875 | button_unlock: DΓ©verrouiller | |
875 | button_download: TΓ©lΓ©charger |
|
876 | button_download: TΓ©lΓ©charger | |
876 | button_list: Lister |
|
877 | button_list: Lister | |
877 | button_view: Voir |
|
878 | button_view: Voir | |
878 | button_move: DΓ©placer |
|
879 | button_move: DΓ©placer | |
879 | button_move_and_follow: DΓ©placer et suivre |
|
880 | button_move_and_follow: DΓ©placer et suivre | |
880 | button_back: Retour |
|
881 | button_back: Retour | |
881 | button_cancel: Annuler |
|
882 | button_cancel: Annuler | |
882 | button_activate: Activer |
|
883 | button_activate: Activer | |
883 | button_sort: Trier |
|
884 | button_sort: Trier | |
884 | button_log_time: Saisir temps |
|
885 | button_log_time: Saisir temps | |
885 | button_rollback: Revenir Γ cette version |
|
886 | button_rollback: Revenir Γ cette version | |
886 | button_watch: Surveiller |
|
887 | button_watch: Surveiller | |
887 | button_unwatch: Ne plus surveiller |
|
888 | button_unwatch: Ne plus surveiller | |
888 | button_reply: RΓ©pondre |
|
889 | button_reply: RΓ©pondre | |
889 | button_archive: Archiver |
|
890 | button_archive: Archiver | |
890 | button_unarchive: DΓ©sarchiver |
|
891 | button_unarchive: DΓ©sarchiver | |
891 | button_reset: RΓ©initialiser |
|
892 | button_reset: RΓ©initialiser | |
892 | button_rename: Renommer |
|
893 | button_rename: Renommer | |
893 | button_change_password: Changer de mot de passe |
|
894 | button_change_password: Changer de mot de passe | |
894 | button_copy: Copier |
|
895 | button_copy: Copier | |
895 | button_copy_and_follow: Copier et suivre |
|
896 | button_copy_and_follow: Copier et suivre | |
896 | button_annotate: Annoter |
|
897 | button_annotate: Annoter | |
897 | button_update: Mettre Γ jour |
|
898 | button_update: Mettre Γ jour | |
898 | button_configure: Configurer |
|
899 | button_configure: Configurer | |
899 | button_quote: Citer |
|
900 | button_quote: Citer | |
900 | button_duplicate: Dupliquer |
|
901 | button_duplicate: Dupliquer | |
901 | button_show: Afficher |
|
902 | button_show: Afficher | |
902 | button_edit_section: Modifier cette section |
|
903 | button_edit_section: Modifier cette section | |
903 | button_export: Exporter |
|
904 | button_export: Exporter | |
904 | button_delete_my_account: Supprimer mon compte |
|
905 | button_delete_my_account: Supprimer mon compte | |
905 | button_close: Fermer |
|
906 | button_close: Fermer | |
906 | button_reopen: RΓ©ouvrir |
|
907 | button_reopen: RΓ©ouvrir | |
907 |
|
908 | |||
908 | status_active: actif |
|
909 | status_active: actif | |
909 | status_registered: enregistrΓ© |
|
910 | status_registered: enregistrΓ© | |
910 | status_locked: verrouillΓ© |
|
911 | status_locked: verrouillΓ© | |
911 |
|
912 | |||
912 | project_status_active: actif |
|
913 | project_status_active: actif | |
913 | project_status_closed: fermΓ© |
|
914 | project_status_closed: fermΓ© | |
914 | project_status_archived: archivΓ© |
|
915 | project_status_archived: archivΓ© | |
915 |
|
916 | |||
916 | version_status_open: ouvert |
|
917 | version_status_open: ouvert | |
917 | version_status_locked: verrouillΓ© |
|
918 | version_status_locked: verrouillΓ© | |
918 | version_status_closed: fermΓ© |
|
919 | version_status_closed: fermΓ© | |
919 |
|
920 | |||
920 | text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e |
|
921 | text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e | |
921 | text_regexp_info: ex. ^[A-Z0-9]+$ |
|
922 | text_regexp_info: ex. ^[A-Z0-9]+$ | |
922 | text_min_max_length_info: 0 pour aucune restriction |
|
923 | text_min_max_length_info: 0 pour aucune restriction | |
923 | text_project_destroy_confirmation: Γtes-vous sΓ»r de vouloir supprimer ce projet et toutes ses donnΓ©es ? |
|
924 | text_project_destroy_confirmation: Γtes-vous sΓ»r de vouloir supprimer ce projet et toutes ses donnΓ©es ? | |
924 | text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront Γ©galement supprimΓ©s." |
|
925 | text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront Γ©galement supprimΓ©s." | |
925 | text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow |
|
926 | text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow | |
926 | text_are_you_sure: Γtes-vous sΓ»r ? |
|
927 | text_are_you_sure: Γtes-vous sΓ»r ? | |
927 | text_tip_issue_begin_day: tΓ’che commenΓ§ant ce jour |
|
928 | text_tip_issue_begin_day: tΓ’che commenΓ§ant ce jour | |
928 | text_tip_issue_end_day: tΓ’che finissant ce jour |
|
929 | text_tip_issue_end_day: tΓ’che finissant ce jour | |
929 | text_tip_issue_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour |
|
930 | text_tip_issue_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour | |
930 | 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Γ©.' |
|
931 | 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Γ©.' | |
931 | text_caracters_maximum: "%{count} caractères maximum." |
|
932 | text_caracters_maximum: "%{count} caractères maximum." | |
932 | text_caracters_minimum: "%{count} caractères minimum." |
|
933 | text_caracters_minimum: "%{count} caractères minimum." | |
933 | text_length_between: "Longueur comprise entre %{min} et %{max} caractères." |
|
934 | text_length_between: "Longueur comprise entre %{min} et %{max} caractères." | |
934 | text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker |
|
935 | text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker | |
935 | text_unallowed_characters: Caractères non autorisés |
|
936 | text_unallowed_characters: Caractères non autorisés | |
936 | text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules). |
|
937 | text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules). | |
937 | text_line_separated: Plusieurs valeurs possibles (une valeur par ligne). |
|
938 | text_line_separated: Plusieurs valeurs possibles (une valeur par ligne). | |
938 | text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits |
|
939 | text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits | |
939 | text_issue_added: "La demande %{id} a Γ©tΓ© soumise par %{author}." |
|
940 | text_issue_added: "La demande %{id} a Γ©tΓ© soumise par %{author}." | |
940 | text_issue_updated: "La demande %{id} a Γ©tΓ© mise Γ jour par %{author}." |
|
941 | text_issue_updated: "La demande %{id} a Γ©tΓ© mise Γ jour par %{author}." | |
941 | text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ? |
|
942 | text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ? | |
942 | text_issue_category_destroy_question: "%{count} demandes sont affectΓ©es Γ cette catΓ©gorie. Que voulez-vous faire ?" |
|
943 | text_issue_category_destroy_question: "%{count} demandes sont affectΓ©es Γ cette catΓ©gorie. Que voulez-vous faire ?" | |
943 | text_issue_category_destroy_assignments: N'affecter les demandes Γ aucune autre catΓ©gorie |
|
944 | text_issue_category_destroy_assignments: N'affecter les demandes Γ aucune autre catΓ©gorie | |
944 | text_issue_category_reassign_to: RΓ©affecter les demandes Γ cette catΓ©gorie |
|
945 | text_issue_category_reassign_to: RΓ©affecter les demandes Γ cette catΓ©gorie | |
945 | 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)." |
|
946 | 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)." | |
946 | 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Γ©." |
|
947 | 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Γ©." | |
947 | text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut |
|
948 | text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut | |
948 | text_status_changed_by_changeset: "AppliquΓ© par commit %{value}." |
|
949 | text_status_changed_by_changeset: "AppliquΓ© par commit %{value}." | |
949 | text_time_logged_by_changeset: "AppliquΓ© par commit %{value}" |
|
950 | text_time_logged_by_changeset: "AppliquΓ© par commit %{value}" | |
950 | text_issues_destroy_confirmation: 'Γtes-vous sΓ»r de vouloir supprimer la ou les demandes(s) selectionnΓ©e(s) ?' |
|
951 | text_issues_destroy_confirmation: 'Γtes-vous sΓ»r de vouloir supprimer la ou les demandes(s) selectionnΓ©e(s) ?' | |
951 | text_issues_destroy_descendants_confirmation: "Cela entrainera Γ©galement la suppression de %{count} sous-tΓ’che(s)." |
|
952 | text_issues_destroy_descendants_confirmation: "Cela entrainera Γ©galement la suppression de %{count} sous-tΓ’che(s)." | |
952 | text_select_project_modules: 'SΓ©lectionner les modules Γ activer pour ce projet :' |
|
953 | text_select_project_modules: 'SΓ©lectionner les modules Γ activer pour ce projet :' | |
953 | text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ© |
|
954 | text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ© | |
954 | text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture |
|
955 | text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture | |
955 | text_plugin_assets_writable: RΓ©pertoire public des plugins accessible en Γ©criture |
|
956 | text_plugin_assets_writable: RΓ©pertoire public des plugins accessible en Γ©criture | |
956 | text_rmagick_available: Bibliothèque RMagick présente (optionnelle) |
|
957 | text_rmagick_available: Bibliothèque RMagick présente (optionnelle) | |
957 | text_destroy_time_entries_question: "%{hours} heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ supprimer. Que voulez-vous faire ?" |
|
958 | text_destroy_time_entries_question: "%{hours} heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ supprimer. Que voulez-vous faire ?" | |
958 | text_destroy_time_entries: Supprimer les heures |
|
959 | text_destroy_time_entries: Supprimer les heures | |
959 | text_assign_time_entries_to_project: Reporter les heures sur le projet |
|
960 | text_assign_time_entries_to_project: Reporter les heures sur le projet | |
960 | text_reassign_time_entries: 'Reporter les heures sur cette demande:' |
|
961 | text_reassign_time_entries: 'Reporter les heures sur cette demande:' | |
961 | text_user_wrote: "%{value} a Γ©crit :" |
|
962 | text_user_wrote: "%{value} a Γ©crit :" | |
962 | text_enumeration_destroy_question: "Cette valeur est affectΓ©e Γ %{count} objets." |
|
963 | text_enumeration_destroy_question: "Cette valeur est affectΓ©e Γ %{count} objets." | |
963 | text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ cette valeur:' |
|
964 | text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ cette valeur:' | |
964 | 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." |
|
965 | 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." | |
965 | 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." |
|
966 | 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." | |
966 | text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.' |
|
967 | text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.' | |
967 | text_custom_field_possible_values_info: 'Une ligne par valeur' |
|
968 | text_custom_field_possible_values_info: 'Une ligne par valeur' | |
968 | text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?" |
|
969 | text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?" | |
969 | text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines" |
|
970 | text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines" | |
970 | text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes" |
|
971 | text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes" | |
971 | text_wiki_page_reassign_children: "RΓ©affecter les sous-pages Γ cette page" |
|
972 | text_wiki_page_reassign_children: "RΓ©affecter les sous-pages Γ cette page" | |
972 | 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 ?" |
|
973 | 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 ?" | |
973 | text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardΓ© qui sera perdu si vous quittez la page." |
|
974 | text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardΓ© qui sera perdu si vous quittez la page." | |
974 | 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)" |
|
975 | 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)" | |
975 | text_issue_conflict_resolution_add_notes: "Ajouter mes notes et ignorer mes autres changements" |
|
976 | text_issue_conflict_resolution_add_notes: "Ajouter mes notes et ignorer mes autres changements" | |
976 | text_issue_conflict_resolution_cancel: "Annuler ma mise Γ jour et rΓ©afficher %{link}" |
|
977 | text_issue_conflict_resolution_cancel: "Annuler ma mise Γ jour et rΓ©afficher %{link}" | |
977 | text_account_destroy_confirmation: "Γtes-vous sΓ»r de vouloir continuer ?\nVotre compte sera dΓ©finitivement supprimΓ©, sans aucune possibilitΓ© de le rΓ©activer." |
|
978 | text_account_destroy_confirmation: "Γtes-vous sΓ»r de vouloir continuer ?\nVotre compte sera dΓ©finitivement supprimΓ©, sans aucune possibilitΓ© de le rΓ©activer." | |
978 | 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." |
|
979 | 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." | |
979 | text_project_closed: Ce projet est fermΓ© et accessible en lecture seule. |
|
980 | text_project_closed: Ce projet est fermΓ© et accessible en lecture seule. | |
980 |
|
981 | |||
981 | default_role_manager: "Manager " |
|
982 | default_role_manager: "Manager " | |
982 | default_role_developer: "DΓ©veloppeur " |
|
983 | default_role_developer: "DΓ©veloppeur " | |
983 | default_role_reporter: "Rapporteur " |
|
984 | default_role_reporter: "Rapporteur " | |
984 | default_tracker_bug: Anomalie |
|
985 | default_tracker_bug: Anomalie | |
985 | default_tracker_feature: Evolution |
|
986 | default_tracker_feature: Evolution | |
986 | default_tracker_support: Assistance |
|
987 | default_tracker_support: Assistance | |
987 | default_issue_status_new: Nouveau |
|
988 | default_issue_status_new: Nouveau | |
988 | default_issue_status_in_progress: En cours |
|
989 | default_issue_status_in_progress: En cours | |
989 | default_issue_status_resolved: RΓ©solu |
|
990 | default_issue_status_resolved: RΓ©solu | |
990 | default_issue_status_feedback: Commentaire |
|
991 | default_issue_status_feedback: Commentaire | |
991 | default_issue_status_closed: FermΓ© |
|
992 | default_issue_status_closed: FermΓ© | |
992 | default_issue_status_rejected: RejetΓ© |
|
993 | default_issue_status_rejected: RejetΓ© | |
993 | default_doc_category_user: Documentation utilisateur |
|
994 | default_doc_category_user: Documentation utilisateur | |
994 | default_doc_category_tech: Documentation technique |
|
995 | default_doc_category_tech: Documentation technique | |
995 | default_priority_low: Bas |
|
996 | default_priority_low: Bas | |
996 | default_priority_normal: Normal |
|
997 | default_priority_normal: Normal | |
997 | default_priority_high: Haut |
|
998 | default_priority_high: Haut | |
998 | default_priority_urgent: Urgent |
|
999 | default_priority_urgent: Urgent | |
999 | default_priority_immediate: ImmΓ©diat |
|
1000 | default_priority_immediate: ImmΓ©diat | |
1000 | default_activity_design: Conception |
|
1001 | default_activity_design: Conception | |
1001 | default_activity_development: DΓ©veloppement |
|
1002 | default_activity_development: DΓ©veloppement | |
1002 |
|
1003 | |||
1003 | enumeration_issue_priorities: PrioritΓ©s des demandes |
|
1004 | enumeration_issue_priorities: PrioritΓ©s des demandes | |
1004 | enumeration_doc_categories: CatΓ©gories des documents |
|
1005 | enumeration_doc_categories: CatΓ©gories des documents | |
1005 | enumeration_activities: ActivitΓ©s (suivi du temps) |
|
1006 | enumeration_activities: ActivitΓ©s (suivi du temps) | |
1006 | label_greater_or_equal: ">=" |
|
1007 | label_greater_or_equal: ">=" | |
1007 | label_less_or_equal: "<=" |
|
1008 | label_less_or_equal: "<=" | |
1008 | label_between: entre |
|
1009 | label_between: entre | |
1009 | label_view_all_revisions: Voir toutes les rΓ©visions |
|
1010 | label_view_all_revisions: Voir toutes les rΓ©visions | |
1010 | label_tag: Tag |
|
1011 | label_tag: Tag | |
1011 | label_branch: Branche |
|
1012 | label_branch: Branche | |
1012 | error_no_tracker_in_project: "Aucun tracker n'est associΓ© Γ ce projet. VΓ©rifier la configuration du projet." |
|
1013 | error_no_tracker_in_project: "Aucun tracker n'est associΓ© Γ ce projet. VΓ©rifier la configuration du projet." | |
1013 | error_no_default_issue_status: "Aucun statut de demande n'est dΓ©fini par dΓ©faut. VΓ©rifier votre configuration (Administration -> Statuts de demandes)." |
|
1014 | error_no_default_issue_status: "Aucun statut de demande n'est dΓ©fini par dΓ©faut. VΓ©rifier votre configuration (Administration -> Statuts de demandes)." | |
1014 | text_journal_changed: "%{label} changΓ© de %{old} Γ %{new}" |
|
1015 | text_journal_changed: "%{label} changΓ© de %{old} Γ %{new}" | |
1015 | text_journal_changed_no_detail: "%{label} mis Γ jour" |
|
1016 | text_journal_changed_no_detail: "%{label} mis Γ jour" | |
1016 | text_journal_set_to: "%{label} mis Γ %{value}" |
|
1017 | text_journal_set_to: "%{label} mis Γ %{value}" | |
1017 | text_journal_deleted: "%{label} %{old} supprimΓ©" |
|
1018 | text_journal_deleted: "%{label} %{old} supprimΓ©" | |
1018 | text_journal_added: "%{label} %{value} ajoutΓ©" |
|
1019 | text_journal_added: "%{label} %{value} ajoutΓ©" | |
1019 | enumeration_system_activity: Activité système |
|
1020 | enumeration_system_activity: Activité système | |
1020 | label_board_sticky: Sticky |
|
1021 | label_board_sticky: Sticky | |
1021 | label_board_locked: VerrouillΓ© |
|
1022 | label_board_locked: VerrouillΓ© | |
1022 | error_unable_delete_issue_status: Impossible de supprimer le statut de demande |
|
1023 | error_unable_delete_issue_status: Impossible de supprimer le statut de demande | |
1023 | error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisΓ© |
|
1024 | error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisΓ© | |
1024 | error_unable_to_connect: Connexion impossible (%{value}) |
|
1025 | error_unable_to_connect: Connexion impossible (%{value}) | |
1025 | error_can_not_remove_role: Ce rΓ΄le est utilisΓ© et ne peut pas Γͺtre supprimΓ©. |
|
1026 | error_can_not_remove_role: Ce rΓ΄le est utilisΓ© et ne peut pas Γͺtre supprimΓ©. | |
1026 | error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas Γͺtre supprimΓ©. |
|
1027 | error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas Γͺtre supprimΓ©. | |
1027 | field_principal: Principal |
|
1028 | field_principal: Principal | |
1028 | notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}." |
|
1029 | notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}." | |
1029 | text_zoom_out: Zoom arrière |
|
1030 | text_zoom_out: Zoom arrière | |
1030 | text_zoom_in: Zoom avant |
|
1031 | text_zoom_in: Zoom avant | |
1031 | notice_unable_delete_time_entry: Impossible de supprimer le temps passΓ©. |
|
1032 | notice_unable_delete_time_entry: Impossible de supprimer le temps passΓ©. | |
1032 | label_overall_spent_time: Temps passΓ© global |
|
1033 | label_overall_spent_time: Temps passΓ© global | |
1033 | field_time_entries: Temps passΓ© |
|
1034 | field_time_entries: Temps passΓ© | |
1034 | project_module_gantt: Gantt |
|
1035 | project_module_gantt: Gantt | |
1035 | project_module_calendar: Calendrier |
|
1036 | project_module_calendar: Calendrier | |
1036 | button_edit_associated_wikipage: "Modifier la page wiki associΓ©e: %{page_title}" |
|
1037 | button_edit_associated_wikipage: "Modifier la page wiki associΓ©e: %{page_title}" | |
1037 | text_are_you_sure_with_children: Supprimer la demande et toutes ses sous-demandes ? |
|
1038 | text_are_you_sure_with_children: Supprimer la demande et toutes ses sous-demandes ? | |
1038 | field_text: Champ texte |
|
1039 | field_text: Champ texte | |
1039 | label_user_mail_option_only_owner: Seulement pour ce que j'ai créé |
|
1040 | label_user_mail_option_only_owner: Seulement pour ce que j'ai créé | |
1040 | setting_default_notification_option: Option de notification par dΓ©faut |
|
1041 | setting_default_notification_option: Option de notification par dΓ©faut | |
1041 | label_user_mail_option_only_my_events: Seulement pour ce que je surveille |
|
1042 | label_user_mail_option_only_my_events: Seulement pour ce que je surveille | |
1042 | label_user_mail_option_only_assigned: Seulement pour ce qui m'est assignΓ© |
|
1043 | label_user_mail_option_only_assigned: Seulement pour ce qui m'est assignΓ© | |
1043 | label_user_mail_option_none: Aucune notification |
|
1044 | label_user_mail_option_none: Aucune notification | |
1044 | field_member_of_group: Groupe de l'assignΓ© |
|
1045 | field_member_of_group: Groupe de l'assignΓ© | |
1045 | field_assigned_to_role: RΓ΄le de l'assignΓ© |
|
1046 | field_assigned_to_role: RΓ΄le de l'assignΓ© | |
1046 | setting_emails_header: En-tΓͺte des emails |
|
1047 | setting_emails_header: En-tΓͺte des emails | |
1047 | label_bulk_edit_selected_time_entries: Modifier les temps passΓ©s sΓ©lectionnΓ©s |
|
1048 | label_bulk_edit_selected_time_entries: Modifier les temps passΓ©s sΓ©lectionnΓ©s | |
1048 | text_time_entries_destroy_confirmation: "Etes-vous sΓ»r de vouloir supprimer les temps passΓ©s sΓ©lectionnΓ©s ?" |
|
1049 | text_time_entries_destroy_confirmation: "Etes-vous sΓ»r de vouloir supprimer les temps passΓ©s sΓ©lectionnΓ©s ?" | |
1049 | field_scm_path_encoding: Encodage des chemins |
|
1050 | field_scm_path_encoding: Encodage des chemins | |
1050 | text_scm_path_encoding_note: "DΓ©faut : UTF-8" |
|
1051 | text_scm_path_encoding_note: "DΓ©faut : UTF-8" | |
1051 | field_path_to_repository: Chemin du dΓ©pΓ΄t |
|
1052 | field_path_to_repository: Chemin du dΓ©pΓ΄t | |
1052 | field_root_directory: RΓ©pertoire racine |
|
1053 | field_root_directory: RΓ©pertoire racine | |
1053 | field_cvs_module: Module |
|
1054 | field_cvs_module: Module | |
1054 | field_cvsroot: CVSROOT |
|
1055 | field_cvsroot: CVSROOT | |
1055 | text_mercurial_repository_note: "DΓ©pΓ΄t local (exemples : /hgrepo, c:\\hgrepo)" |
|
1056 | text_mercurial_repository_note: "DΓ©pΓ΄t local (exemples : /hgrepo, c:\\hgrepo)" | |
1056 | text_scm_command: Commande |
|
1057 | text_scm_command: Commande | |
1057 | text_scm_command_version: Version |
|
1058 | text_scm_command_version: Version | |
1058 | label_git_report_last_commit: Afficher le dernier commit des fichiers et rΓ©pertoires |
|
1059 | label_git_report_last_commit: Afficher le dernier commit des fichiers et rΓ©pertoires | |
1059 | text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification. |
|
1060 | text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification. | |
1060 | text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration. |
|
1061 | text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration. | |
1061 | label_diff: diff |
|
1062 | label_diff: diff | |
1062 | text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) |
|
1063 | text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) | |
1063 | description_query_sort_criteria_direction: Ordre de tri |
|
1064 | description_query_sort_criteria_direction: Ordre de tri | |
1064 | description_project_scope: Périmètre de recherche |
|
1065 | description_project_scope: Périmètre de recherche | |
1065 | description_filter: Filtre |
|
1066 | description_filter: Filtre | |
1066 | description_user_mail_notification: Option de notification |
|
1067 | description_user_mail_notification: Option de notification | |
1067 | description_date_from: Date de dΓ©but |
|
1068 | description_date_from: Date de dΓ©but | |
1068 | description_message_content: Contenu du message |
|
1069 | description_message_content: Contenu du message | |
1069 | description_available_columns: Colonnes disponibles |
|
1070 | description_available_columns: Colonnes disponibles | |
1070 | description_all_columns: Toutes les colonnes |
|
1071 | description_all_columns: Toutes les colonnes | |
1071 | description_date_range_interval: Choisir une pΓ©riode |
|
1072 | description_date_range_interval: Choisir une pΓ©riode | |
1072 | description_issue_category_reassign: Choisir une catΓ©gorie |
|
1073 | description_issue_category_reassign: Choisir une catΓ©gorie | |
1073 | description_search: Champ de recherche |
|
1074 | description_search: Champ de recherche | |
1074 | description_notes: Notes |
|
1075 | description_notes: Notes | |
1075 | description_date_range_list: Choisir une pΓ©riode prΓ©dΓ©finie |
|
1076 | description_date_range_list: Choisir une pΓ©riode prΓ©dΓ©finie | |
1076 | description_choose_project: Projets |
|
1077 | description_choose_project: Projets | |
1077 | description_date_to: Date de fin |
|
1078 | description_date_to: Date de fin | |
1078 | description_query_sort_criteria_attribute: Critère de tri |
|
1079 | description_query_sort_criteria_attribute: Critère de tri | |
1079 | description_wiki_subpages_reassign: Choisir une nouvelle page parent |
|
1080 | description_wiki_subpages_reassign: Choisir une nouvelle page parent | |
1080 | description_selected_columns: Colonnes sΓ©lectionnΓ©es |
|
1081 | description_selected_columns: Colonnes sΓ©lectionnΓ©es | |
1081 | label_parent_revision: Parent |
|
1082 | label_parent_revision: Parent | |
1082 | label_child_revision: Enfant |
|
1083 | label_child_revision: Enfant | |
1083 | error_scm_annotate_big_text_file: Cette entrΓ©e ne peut pas Γͺtre annotΓ©e car elle excΓ¨de la taille maximale. |
|
1084 | error_scm_annotate_big_text_file: Cette entrΓ©e ne peut pas Γͺtre annotΓ©e car elle excΓ¨de la taille maximale. | |
1084 | setting_repositories_encodings: Encodages des fichiers et des dΓ©pΓ΄ts |
|
1085 | setting_repositories_encodings: Encodages des fichiers et des dΓ©pΓ΄ts | |
1085 | label_search_for_watchers: Rechercher des observateurs |
|
1086 | label_search_for_watchers: Rechercher des observateurs | |
1086 | 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Γ©.' |
|
1087 | 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,600 +1,601 | |||||
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]).text(operatorLabels[operators[i]]); |
|
133 | var option = $('<option>').val(operators[i]).text(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"> </span></span>' |
|
146 | ' <span class="toggle-multiselect"> </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]).text(filterValue[0]); |
|
154 | option.val(filterValue[1]).text(filterValue[0]); | |
155 | if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);} |
|
155 | if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);} | |
156 | } else { |
|
156 | } else { | |
157 | option.val(filterValue).text(filterValue); |
|
157 | option.val(filterValue).text(filterValue); | |
158 | if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);} |
|
158 | if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);} | |
159 | } |
|
159 | } | |
160 | select.append(option); |
|
160 | select.append(option); | |
161 | } |
|
161 | } | |
162 | break; |
|
162 | break; | |
163 | case "date": |
|
163 | case "date": | |
164 | case "date_past": |
|
164 | case "date_past": | |
165 | tr.find('td.values').append( |
|
165 | tr.find('td.values').append( | |
166 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' + |
|
166 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' + | |
167 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' + |
|
167 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' + | |
168 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>' |
|
168 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>' | |
169 | ); |
|
169 | ); | |
170 | $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions); |
|
170 | $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions); | |
171 | $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions); |
|
171 | $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions); | |
172 | $('#values_'+fieldId).val(values[0]); |
|
172 | $('#values_'+fieldId).val(values[0]); | |
173 | break; |
|
173 | break; | |
174 | case "string": |
|
174 | case "string": | |
175 | case "text": |
|
175 | case "text": | |
176 | tr.find('td.values').append( |
|
176 | tr.find('td.values').append( | |
177 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>' |
|
177 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>' | |
178 | ); |
|
178 | ); | |
179 | $('#values_'+fieldId).val(values[0]); |
|
179 | $('#values_'+fieldId).val(values[0]); | |
180 | break; |
|
180 | break; | |
181 | case "relation": |
|
181 | case "relation": | |
182 | tr.find('td.values').append( |
|
182 | tr.find('td.values').append( | |
183 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="6" class="value" /></span>' + |
|
183 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="6" class="value" /></span>' + | |
184 | '<span style="display:none;"><select class="value" name="v['+field+'][]" id="values_'+fieldId+'_1"></select></span>' |
|
184 | '<span style="display:none;"><select class="value" name="v['+field+'][]" id="values_'+fieldId+'_1"></select></span>' | |
185 | ); |
|
185 | ); | |
186 | $('#values_'+fieldId).val(values[0]); |
|
186 | $('#values_'+fieldId).val(values[0]); | |
187 | select = tr.find('td.values select'); |
|
187 | select = tr.find('td.values select'); | |
188 | for (i=0;i<allProjects.length;i++){ |
|
188 | for (i=0;i<allProjects.length;i++){ | |
189 | var filterValue = allProjects[i]; |
|
189 | var filterValue = allProjects[i]; | |
190 | var option = $('<option>'); |
|
190 | var option = $('<option>'); | |
191 | option.val(filterValue[1]).text(filterValue[0]); |
|
191 | option.val(filterValue[1]).text(filterValue[0]); | |
192 | if (values[0] == filterValue[1]) {option.attr('selected', true)}; |
|
192 | if (values[0] == filterValue[1]) {option.attr('selected', true)}; | |
193 | select.append(option); |
|
193 | select.append(option); | |
194 | } |
|
194 | } | |
195 | case "integer": |
|
195 | case "integer": | |
196 | case "float": |
|
196 | case "float": | |
197 | tr.find('td.values').append( |
|
197 | tr.find('td.values').append( | |
198 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" /></span>' + |
|
198 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" /></span>' + | |
199 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" /></span>' |
|
199 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" /></span>' | |
200 | ); |
|
200 | ); | |
201 | $('#values_'+fieldId+'_1').val(values[0]); |
|
201 | $('#values_'+fieldId+'_1').val(values[0]); | |
202 | $('#values_'+fieldId+'_2').val(values[1]); |
|
202 | $('#values_'+fieldId+'_2').val(values[1]); | |
203 | break; |
|
203 | break; | |
204 | } |
|
204 | } | |
205 | } |
|
205 | } | |
206 |
|
206 | |||
207 | function toggleFilter(field) { |
|
207 | function toggleFilter(field) { | |
208 | var fieldId = field.replace('.', '_'); |
|
208 | var fieldId = field.replace('.', '_'); | |
209 | if ($('#cb_' + fieldId).is(':checked')) { |
|
209 | if ($('#cb_' + fieldId).is(':checked')) { | |
210 | $("#operators_" + fieldId).show().removeAttr('disabled'); |
|
210 | $("#operators_" + fieldId).show().removeAttr('disabled'); | |
211 | toggleOperator(field); |
|
211 | toggleOperator(field); | |
212 | } else { |
|
212 | } else { | |
213 | $("#operators_" + fieldId).hide().attr('disabled', true); |
|
213 | $("#operators_" + fieldId).hide().attr('disabled', true); | |
214 | enableValues(field, []); |
|
214 | enableValues(field, []); | |
215 | } |
|
215 | } | |
216 | } |
|
216 | } | |
217 |
|
217 | |||
218 | function enableValues(field, indexes) { |
|
218 | function enableValues(field, indexes) { | |
219 | var fieldId = field.replace('.', '_'); |
|
219 | var fieldId = field.replace('.', '_'); | |
220 | $('#tr_'+fieldId+' td.values .value').each(function(index) { |
|
220 | $('#tr_'+fieldId+' td.values .value').each(function(index) { | |
221 | if ($.inArray(index, indexes) >= 0) { |
|
221 | if ($.inArray(index, indexes) >= 0) { | |
222 | $(this).removeAttr('disabled'); |
|
222 | $(this).removeAttr('disabled'); | |
223 | $(this).parents('span').first().show(); |
|
223 | $(this).parents('span').first().show(); | |
224 | } else { |
|
224 | } else { | |
225 | $(this).val(''); |
|
225 | $(this).val(''); | |
226 | $(this).attr('disabled', true); |
|
226 | $(this).attr('disabled', true); | |
227 | $(this).parents('span').first().hide(); |
|
227 | $(this).parents('span').first().hide(); | |
228 | } |
|
228 | } | |
229 |
|
229 | |||
230 | if ($(this).hasClass('group')) { |
|
230 | if ($(this).hasClass('group')) { | |
231 | $(this).addClass('open'); |
|
231 | $(this).addClass('open'); | |
232 | } else { |
|
232 | } else { | |
233 | $(this).show(); |
|
233 | $(this).show(); | |
234 | } |
|
234 | } | |
235 | }); |
|
235 | }); | |
236 | } |
|
236 | } | |
237 |
|
237 | |||
238 | function toggleOperator(field) { |
|
238 | function toggleOperator(field) { | |
239 | var fieldId = field.replace('.', '_'); |
|
239 | var fieldId = field.replace('.', '_'); | |
240 | var operator = $("#operators_" + fieldId); |
|
240 | var operator = $("#operators_" + fieldId); | |
241 | switch (operator.val()) { |
|
241 | switch (operator.val()) { | |
242 | case "!*": |
|
242 | case "!*": | |
243 | case "*": |
|
243 | case "*": | |
244 | case "t": |
|
244 | case "t": | |
245 | case "w": |
|
245 | case "w": | |
246 | case "o": |
|
246 | case "o": | |
247 | case "c": |
|
247 | case "c": | |
248 | enableValues(field, []); |
|
248 | enableValues(field, []); | |
249 | break; |
|
249 | break; | |
250 | case "><": |
|
250 | case "><": | |
251 | enableValues(field, [0,1]); |
|
251 | enableValues(field, [0,1]); | |
252 | break; |
|
252 | break; | |
253 | case "<t+": |
|
253 | case "<t+": | |
254 | case ">t+": |
|
254 | case ">t+": | |
255 | case "t+": |
|
255 | case "t+": | |
256 | case ">t-": |
|
256 | case ">t-": | |
257 | case "<t-": |
|
257 | case "<t-": | |
258 | case "t-": |
|
258 | case "t-": | |
259 | enableValues(field, [2]); |
|
259 | enableValues(field, [2]); | |
260 | break; |
|
260 | break; | |
261 | case "=p": |
|
261 | case "=p": | |
262 | case "=!p": |
|
262 | case "=!p": | |
|
263 | case "!p": | |||
263 | enableValues(field, [1]); |
|
264 | enableValues(field, [1]); | |
264 | break; |
|
265 | break; | |
265 | default: |
|
266 | default: | |
266 | enableValues(field, [0]); |
|
267 | enableValues(field, [0]); | |
267 | break; |
|
268 | break; | |
268 | } |
|
269 | } | |
269 | } |
|
270 | } | |
270 |
|
271 | |||
271 | function toggleMultiSelect(el) { |
|
272 | function toggleMultiSelect(el) { | |
272 | if (el.attr('multiple')) { |
|
273 | if (el.attr('multiple')) { | |
273 | el.removeAttr('multiple'); |
|
274 | el.removeAttr('multiple'); | |
274 | } else { |
|
275 | } else { | |
275 | el.attr('multiple', true); |
|
276 | el.attr('multiple', true); | |
276 | } |
|
277 | } | |
277 | } |
|
278 | } | |
278 |
|
279 | |||
279 | function submit_query_form(id) { |
|
280 | function submit_query_form(id) { | |
280 | selectAllOptions("selected_columns"); |
|
281 | selectAllOptions("selected_columns"); | |
281 | $('#'+id).submit(); |
|
282 | $('#'+id).submit(); | |
282 | } |
|
283 | } | |
283 |
|
284 | |||
284 | var fileFieldCount = 1; |
|
285 | var fileFieldCount = 1; | |
285 | function addFileField() { |
|
286 | function addFileField() { | |
286 | var fields = $('#attachments_fields'); |
|
287 | var fields = $('#attachments_fields'); | |
287 | if (fields.children().length >= 10) return false; |
|
288 | if (fields.children().length >= 10) return false; | |
288 | fileFieldCount++; |
|
289 | fileFieldCount++; | |
289 | var s = fields.children('span').first().clone(); |
|
290 | var s = fields.children('span').first().clone(); | |
290 | s.children('input.file').attr('name', "attachments[" + fileFieldCount + "][file]").val(''); |
|
291 | s.children('input.file').attr('name', "attachments[" + fileFieldCount + "][file]").val(''); | |
291 | s.children('input.description').attr('name', "attachments[" + fileFieldCount + "][description]").val(''); |
|
292 | s.children('input.description').attr('name', "attachments[" + fileFieldCount + "][description]").val(''); | |
292 | fields.append(s); |
|
293 | fields.append(s); | |
293 | } |
|
294 | } | |
294 |
|
295 | |||
295 | function removeFileField(el) { |
|
296 | function removeFileField(el) { | |
296 | var fields = $('#attachments_fields'); |
|
297 | var fields = $('#attachments_fields'); | |
297 | var s = $(el).parents('span').first(); |
|
298 | var s = $(el).parents('span').first(); | |
298 | if (fields.children().length > 1) { |
|
299 | if (fields.children().length > 1) { | |
299 | s.remove(); |
|
300 | s.remove(); | |
300 | } else { |
|
301 | } else { | |
301 | s.children('input.file').val(''); |
|
302 | s.children('input.file').val(''); | |
302 | s.children('input.description').val(''); |
|
303 | s.children('input.description').val(''); | |
303 | } |
|
304 | } | |
304 | } |
|
305 | } | |
305 |
|
306 | |||
306 | function checkFileSize(el, maxSize, message) { |
|
307 | function checkFileSize(el, maxSize, message) { | |
307 | var files = el.files; |
|
308 | var files = el.files; | |
308 | if (files) { |
|
309 | if (files) { | |
309 | for (var i=0; i<files.length; i++) { |
|
310 | for (var i=0; i<files.length; i++) { | |
310 | if (files[i].size > maxSize) { |
|
311 | if (files[i].size > maxSize) { | |
311 | alert(message); |
|
312 | alert(message); | |
312 | el.value = ""; |
|
313 | el.value = ""; | |
313 | } |
|
314 | } | |
314 | } |
|
315 | } | |
315 | } |
|
316 | } | |
316 | } |
|
317 | } | |
317 |
|
318 | |||
318 | function showTab(name) { |
|
319 | function showTab(name) { | |
319 | $('div#content .tab-content').hide(); |
|
320 | $('div#content .tab-content').hide(); | |
320 | $('div.tabs a').removeClass('selected'); |
|
321 | $('div.tabs a').removeClass('selected'); | |
321 | $('#tab-content-' + name).show(); |
|
322 | $('#tab-content-' + name).show(); | |
322 | $('#tab-' + name).addClass('selected'); |
|
323 | $('#tab-' + name).addClass('selected'); | |
323 | return false; |
|
324 | return false; | |
324 | } |
|
325 | } | |
325 |
|
326 | |||
326 | function moveTabRight(el) { |
|
327 | function moveTabRight(el) { | |
327 | var lis = $(el).parents('div.tabs').first().find('ul').children(); |
|
328 | var lis = $(el).parents('div.tabs').first().find('ul').children(); | |
328 | var tabsWidth = 0; |
|
329 | var tabsWidth = 0; | |
329 | var i = 0; |
|
330 | var i = 0; | |
330 | lis.each(function(){ |
|
331 | lis.each(function(){ | |
331 | if ($(this).is(':visible')) { |
|
332 | if ($(this).is(':visible')) { | |
332 | tabsWidth += $(this).width() + 6; |
|
333 | tabsWidth += $(this).width() + 6; | |
333 | } |
|
334 | } | |
334 | }); |
|
335 | }); | |
335 | if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; } |
|
336 | if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; } | |
336 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } |
|
337 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } | |
337 | lis.eq(i).hide(); |
|
338 | lis.eq(i).hide(); | |
338 | } |
|
339 | } | |
339 |
|
340 | |||
340 | function moveTabLeft(el) { |
|
341 | function moveTabLeft(el) { | |
341 | var lis = $(el).parents('div.tabs').first().find('ul').children(); |
|
342 | var lis = $(el).parents('div.tabs').first().find('ul').children(); | |
342 | var i = 0; |
|
343 | var i = 0; | |
343 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } |
|
344 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } | |
344 | if (i>0) { |
|
345 | if (i>0) { | |
345 | lis.eq(i-1).show(); |
|
346 | lis.eq(i-1).show(); | |
346 | } |
|
347 | } | |
347 | } |
|
348 | } | |
348 |
|
349 | |||
349 | function displayTabsButtons() { |
|
350 | function displayTabsButtons() { | |
350 | var lis; |
|
351 | var lis; | |
351 | var tabsWidth = 0; |
|
352 | var tabsWidth = 0; | |
352 | var el; |
|
353 | var el; | |
353 | $('div.tabs').each(function() { |
|
354 | $('div.tabs').each(function() { | |
354 | el = $(this); |
|
355 | el = $(this); | |
355 | lis = el.find('ul').children(); |
|
356 | lis = el.find('ul').children(); | |
356 | lis.each(function(){ |
|
357 | lis.each(function(){ | |
357 | if ($(this).is(':visible')) { |
|
358 | if ($(this).is(':visible')) { | |
358 | tabsWidth += $(this).width() + 6; |
|
359 | tabsWidth += $(this).width() + 6; | |
359 | } |
|
360 | } | |
360 | }); |
|
361 | }); | |
361 | if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) { |
|
362 | if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) { | |
362 | el.find('div.tabs-buttons').hide(); |
|
363 | el.find('div.tabs-buttons').hide(); | |
363 | } else { |
|
364 | } else { | |
364 | el.find('div.tabs-buttons').show(); |
|
365 | el.find('div.tabs-buttons').show(); | |
365 | } |
|
366 | } | |
366 | }); |
|
367 | }); | |
367 | } |
|
368 | } | |
368 |
|
369 | |||
369 | function setPredecessorFieldsVisibility() { |
|
370 | function setPredecessorFieldsVisibility() { | |
370 | var relationType = $('#relation_relation_type'); |
|
371 | var relationType = $('#relation_relation_type'); | |
371 | if (relationType.val() == "precedes" || relationType.val() == "follows") { |
|
372 | if (relationType.val() == "precedes" || relationType.val() == "follows") { | |
372 | $('#predecessor_fields').show(); |
|
373 | $('#predecessor_fields').show(); | |
373 | } else { |
|
374 | } else { | |
374 | $('#predecessor_fields').hide(); |
|
375 | $('#predecessor_fields').hide(); | |
375 | } |
|
376 | } | |
376 | } |
|
377 | } | |
377 |
|
378 | |||
378 | function showModal(id, width) { |
|
379 | function showModal(id, width) { | |
379 | var el = $('#'+id).first(); |
|
380 | var el = $('#'+id).first(); | |
380 | if (el.length == 0 || el.is(':visible')) {return;} |
|
381 | if (el.length == 0 || el.is(':visible')) {return;} | |
381 | var title = el.find('h3.title').text(); |
|
382 | var title = el.find('h3.title').text(); | |
382 | el.dialog({ |
|
383 | el.dialog({ | |
383 | width: width, |
|
384 | width: width, | |
384 | modal: true, |
|
385 | modal: true, | |
385 | resizable: false, |
|
386 | resizable: false, | |
386 | dialogClass: 'modal', |
|
387 | dialogClass: 'modal', | |
387 | title: title |
|
388 | title: title | |
388 | }); |
|
389 | }); | |
389 | el.find("input[type=text], input[type=submit]").first().focus(); |
|
390 | el.find("input[type=text], input[type=submit]").first().focus(); | |
390 | } |
|
391 | } | |
391 |
|
392 | |||
392 | function hideModal(el) { |
|
393 | function hideModal(el) { | |
393 | var modal; |
|
394 | var modal; | |
394 | if (el) { |
|
395 | if (el) { | |
395 | modal = $(el).parents('.ui-dialog-content'); |
|
396 | modal = $(el).parents('.ui-dialog-content'); | |
396 | } else { |
|
397 | } else { | |
397 | modal = $('#ajax-modal'); |
|
398 | modal = $('#ajax-modal'); | |
398 | } |
|
399 | } | |
399 | modal.dialog("close"); |
|
400 | modal.dialog("close"); | |
400 | } |
|
401 | } | |
401 |
|
402 | |||
402 | function submitPreview(url, form, target) { |
|
403 | function submitPreview(url, form, target) { | |
403 | $.ajax({ |
|
404 | $.ajax({ | |
404 | url: url, |
|
405 | url: url, | |
405 | type: 'post', |
|
406 | type: 'post', | |
406 | data: $('#'+form).serialize(), |
|
407 | data: $('#'+form).serialize(), | |
407 | success: function(data){ |
|
408 | success: function(data){ | |
408 | $('#'+target).html(data); |
|
409 | $('#'+target).html(data); | |
409 | } |
|
410 | } | |
410 | }); |
|
411 | }); | |
411 | } |
|
412 | } | |
412 |
|
413 | |||
413 | function collapseScmEntry(id) { |
|
414 | function collapseScmEntry(id) { | |
414 | $('.'+id).each(function() { |
|
415 | $('.'+id).each(function() { | |
415 | if ($(this).hasClass('open')) { |
|
416 | if ($(this).hasClass('open')) { | |
416 | collapseScmEntry($(this).attr('id')); |
|
417 | collapseScmEntry($(this).attr('id')); | |
417 | } |
|
418 | } | |
418 | $(this).hide(); |
|
419 | $(this).hide(); | |
419 | }); |
|
420 | }); | |
420 | $('#'+id).removeClass('open'); |
|
421 | $('#'+id).removeClass('open'); | |
421 | } |
|
422 | } | |
422 |
|
423 | |||
423 | function expandScmEntry(id) { |
|
424 | function expandScmEntry(id) { | |
424 | $('.'+id).each(function() { |
|
425 | $('.'+id).each(function() { | |
425 | $(this).show(); |
|
426 | $(this).show(); | |
426 | if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) { |
|
427 | if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) { | |
427 | expandScmEntry($(this).attr('id')); |
|
428 | expandScmEntry($(this).attr('id')); | |
428 | } |
|
429 | } | |
429 | }); |
|
430 | }); | |
430 | $('#'+id).addClass('open'); |
|
431 | $('#'+id).addClass('open'); | |
431 | } |
|
432 | } | |
432 |
|
433 | |||
433 | function scmEntryClick(id, url) { |
|
434 | function scmEntryClick(id, url) { | |
434 | el = $('#'+id); |
|
435 | el = $('#'+id); | |
435 | if (el.hasClass('open')) { |
|
436 | if (el.hasClass('open')) { | |
436 | collapseScmEntry(id); |
|
437 | collapseScmEntry(id); | |
437 | el.addClass('collapsed'); |
|
438 | el.addClass('collapsed'); | |
438 | return false; |
|
439 | return false; | |
439 | } else if (el.hasClass('loaded')) { |
|
440 | } else if (el.hasClass('loaded')) { | |
440 | expandScmEntry(id); |
|
441 | expandScmEntry(id); | |
441 | el.removeClass('collapsed'); |
|
442 | el.removeClass('collapsed'); | |
442 | return false; |
|
443 | return false; | |
443 | } |
|
444 | } | |
444 | if (el.hasClass('loading')) { |
|
445 | if (el.hasClass('loading')) { | |
445 | return false; |
|
446 | return false; | |
446 | } |
|
447 | } | |
447 | el.addClass('loading'); |
|
448 | el.addClass('loading'); | |
448 | $.ajax({ |
|
449 | $.ajax({ | |
449 | url: url, |
|
450 | url: url, | |
450 | success: function(data){ |
|
451 | success: function(data){ | |
451 | el.after(data); |
|
452 | el.after(data); | |
452 | el.addClass('open').addClass('loaded').removeClass('loading'); |
|
453 | el.addClass('open').addClass('loaded').removeClass('loading'); | |
453 | } |
|
454 | } | |
454 | }); |
|
455 | }); | |
455 | return true; |
|
456 | return true; | |
456 | } |
|
457 | } | |
457 |
|
458 | |||
458 | function randomKey(size) { |
|
459 | function randomKey(size) { | |
459 | 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'); |
|
460 | 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'); | |
460 | var key = ''; |
|
461 | var key = ''; | |
461 | for (i = 0; i < size; i++) { |
|
462 | for (i = 0; i < size; i++) { | |
462 | key += chars[Math.floor(Math.random() * chars.length)]; |
|
463 | key += chars[Math.floor(Math.random() * chars.length)]; | |
463 | } |
|
464 | } | |
464 | return key; |
|
465 | return key; | |
465 | } |
|
466 | } | |
466 |
|
467 | |||
467 | // Can't use Rails' remote select because we need the form data |
|
468 | // Can't use Rails' remote select because we need the form data | |
468 | function updateIssueFrom(url) { |
|
469 | function updateIssueFrom(url) { | |
469 | $.ajax({ |
|
470 | $.ajax({ | |
470 | url: url, |
|
471 | url: url, | |
471 | type: 'post', |
|
472 | type: 'post', | |
472 | data: $('#issue-form').serialize() |
|
473 | data: $('#issue-form').serialize() | |
473 | }); |
|
474 | }); | |
474 | } |
|
475 | } | |
475 |
|
476 | |||
476 | function updateBulkEditFrom(url) { |
|
477 | function updateBulkEditFrom(url) { | |
477 | $.ajax({ |
|
478 | $.ajax({ | |
478 | url: url, |
|
479 | url: url, | |
479 | type: 'post', |
|
480 | type: 'post', | |
480 | data: $('#bulk_edit_form').serialize() |
|
481 | data: $('#bulk_edit_form').serialize() | |
481 | }); |
|
482 | }); | |
482 | } |
|
483 | } | |
483 |
|
484 | |||
484 | function observeAutocompleteField(fieldId, url) { |
|
485 | function observeAutocompleteField(fieldId, url) { | |
485 | $('#'+fieldId).autocomplete({ |
|
486 | $('#'+fieldId).autocomplete({ | |
486 | source: url, |
|
487 | source: url, | |
487 | minLength: 2, |
|
488 | minLength: 2, | |
488 | }); |
|
489 | }); | |
489 | } |
|
490 | } | |
490 |
|
491 | |||
491 | function observeSearchfield(fieldId, targetId, url) { |
|
492 | function observeSearchfield(fieldId, targetId, url) { | |
492 | $('#'+fieldId).each(function() { |
|
493 | $('#'+fieldId).each(function() { | |
493 | var $this = $(this); |
|
494 | var $this = $(this); | |
494 | $this.attr('data-value-was', $this.val()); |
|
495 | $this.attr('data-value-was', $this.val()); | |
495 | var check = function() { |
|
496 | var check = function() { | |
496 | var val = $this.val(); |
|
497 | var val = $this.val(); | |
497 | if ($this.attr('data-value-was') != val){ |
|
498 | if ($this.attr('data-value-was') != val){ | |
498 | $this.attr('data-value-was', val); |
|
499 | $this.attr('data-value-was', val); | |
499 | $.ajax({ |
|
500 | $.ajax({ | |
500 | url: url, |
|
501 | url: url, | |
501 | type: 'get', |
|
502 | type: 'get', | |
502 | data: {q: $this.val()}, |
|
503 | data: {q: $this.val()}, | |
503 | success: function(data){ $('#'+targetId).html(data); }, |
|
504 | success: function(data){ $('#'+targetId).html(data); }, | |
504 | beforeSend: function(){ $this.addClass('ajax-loading'); }, |
|
505 | beforeSend: function(){ $this.addClass('ajax-loading'); }, | |
505 | complete: function(){ $this.removeClass('ajax-loading'); } |
|
506 | complete: function(){ $this.removeClass('ajax-loading'); } | |
506 | }); |
|
507 | }); | |
507 | } |
|
508 | } | |
508 | }; |
|
509 | }; | |
509 | var reset = function() { |
|
510 | var reset = function() { | |
510 | if (timer) { |
|
511 | if (timer) { | |
511 | clearInterval(timer); |
|
512 | clearInterval(timer); | |
512 | timer = setInterval(check, 300); |
|
513 | timer = setInterval(check, 300); | |
513 | } |
|
514 | } | |
514 | }; |
|
515 | }; | |
515 | var timer = setInterval(check, 300); |
|
516 | var timer = setInterval(check, 300); | |
516 | $this.bind('keyup click mousemove', reset); |
|
517 | $this.bind('keyup click mousemove', reset); | |
517 | }); |
|
518 | }); | |
518 | } |
|
519 | } | |
519 |
|
520 | |||
520 | function observeProjectModules() { |
|
521 | function observeProjectModules() { | |
521 | var f = function() { |
|
522 | var f = function() { | |
522 | /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */ |
|
523 | /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */ | |
523 | if ($('#project_enabled_module_names_issue_tracking').attr('checked')) { |
|
524 | if ($('#project_enabled_module_names_issue_tracking').attr('checked')) { | |
524 | $('#project_trackers').show(); |
|
525 | $('#project_trackers').show(); | |
525 | }else{ |
|
526 | }else{ | |
526 | $('#project_trackers').hide(); |
|
527 | $('#project_trackers').hide(); | |
527 | } |
|
528 | } | |
528 | }; |
|
529 | }; | |
529 |
|
530 | |||
530 | $(window).load(f); |
|
531 | $(window).load(f); | |
531 | $('#project_enabled_module_names_issue_tracking').change(f); |
|
532 | $('#project_enabled_module_names_issue_tracking').change(f); | |
532 | } |
|
533 | } | |
533 |
|
534 | |||
534 | function initMyPageSortable(list, url) { |
|
535 | function initMyPageSortable(list, url) { | |
535 | $('#list-'+list).sortable({ |
|
536 | $('#list-'+list).sortable({ | |
536 | connectWith: '.block-receiver', |
|
537 | connectWith: '.block-receiver', | |
537 | tolerance: 'pointer', |
|
538 | tolerance: 'pointer', | |
538 | update: function(){ |
|
539 | update: function(){ | |
539 | $.ajax({ |
|
540 | $.ajax({ | |
540 | url: url, |
|
541 | url: url, | |
541 | type: 'post', |
|
542 | type: 'post', | |
542 | data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})} |
|
543 | data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})} | |
543 | }); |
|
544 | }); | |
544 | } |
|
545 | } | |
545 | }); |
|
546 | }); | |
546 | $("#list-top, #list-left, #list-right").disableSelection(); |
|
547 | $("#list-top, #list-left, #list-right").disableSelection(); | |
547 | } |
|
548 | } | |
548 |
|
549 | |||
549 | var warnLeavingUnsavedMessage; |
|
550 | var warnLeavingUnsavedMessage; | |
550 | function warnLeavingUnsaved(message) { |
|
551 | function warnLeavingUnsaved(message) { | |
551 | warnLeavingUnsavedMessage = message; |
|
552 | warnLeavingUnsavedMessage = message; | |
552 |
|
553 | |||
553 | $('form').submit(function(){ |
|
554 | $('form').submit(function(){ | |
554 | $('textarea').removeData('changed'); |
|
555 | $('textarea').removeData('changed'); | |
555 | }); |
|
556 | }); | |
556 | $('textarea').change(function(){ |
|
557 | $('textarea').change(function(){ | |
557 | $(this).data('changed', 'changed'); |
|
558 | $(this).data('changed', 'changed'); | |
558 | }); |
|
559 | }); | |
559 | window.onbeforeunload = function(){ |
|
560 | window.onbeforeunload = function(){ | |
560 | var warn = false; |
|
561 | var warn = false; | |
561 | $('textarea').blur().each(function(){ |
|
562 | $('textarea').blur().each(function(){ | |
562 | if ($(this).data('changed')) { |
|
563 | if ($(this).data('changed')) { | |
563 | warn = true; |
|
564 | warn = true; | |
564 | } |
|
565 | } | |
565 | }); |
|
566 | }); | |
566 | if (warn) {return warnLeavingUnsavedMessage;} |
|
567 | if (warn) {return warnLeavingUnsavedMessage;} | |
567 | }; |
|
568 | }; | |
568 | }; |
|
569 | }; | |
569 |
|
570 | |||
570 | $(document).ready(function(){ |
|
571 | $(document).ready(function(){ | |
571 | $('#ajax-indicator').bind('ajaxSend', function(){ |
|
572 | $('#ajax-indicator').bind('ajaxSend', function(){ | |
572 | if ($('.ajax-loading').length == 0) { |
|
573 | if ($('.ajax-loading').length == 0) { | |
573 | $('#ajax-indicator').show(); |
|
574 | $('#ajax-indicator').show(); | |
574 | } |
|
575 | } | |
575 | }); |
|
576 | }); | |
576 | $('#ajax-indicator').bind('ajaxStop', function(){ |
|
577 | $('#ajax-indicator').bind('ajaxStop', function(){ | |
577 | $('#ajax-indicator').hide(); |
|
578 | $('#ajax-indicator').hide(); | |
578 | }); |
|
579 | }); | |
579 | }); |
|
580 | }); | |
580 |
|
581 | |||
581 | function hideOnLoad() { |
|
582 | function hideOnLoad() { | |
582 | $('.hol').hide(); |
|
583 | $('.hol').hide(); | |
583 | } |
|
584 | } | |
584 |
|
585 | |||
585 | function addFormObserversForDoubleSubmit() { |
|
586 | function addFormObserversForDoubleSubmit() { | |
586 | $('form[method=post]').each(function() { |
|
587 | $('form[method=post]').each(function() { | |
587 | if (!$(this).hasClass('multiple-submit')) { |
|
588 | if (!$(this).hasClass('multiple-submit')) { | |
588 | $(this).submit(function(form_submission) { |
|
589 | $(this).submit(function(form_submission) { | |
589 | if ($(form_submission.target).attr('data-submitted')) { |
|
590 | if ($(form_submission.target).attr('data-submitted')) { | |
590 | form_submission.preventDefault(); |
|
591 | form_submission.preventDefault(); | |
591 | } else { |
|
592 | } else { | |
592 | $(form_submission.target).attr('data-submitted', true); |
|
593 | $(form_submission.target).attr('data-submitted', true); | |
593 | } |
|
594 | } | |
594 | }); |
|
595 | }); | |
595 | } |
|
596 | } | |
596 | }); |
|
597 | }); | |
597 | } |
|
598 | } | |
598 |
|
599 | |||
599 | $(document).ready(hideOnLoad); |
|
600 | $(document).ready(hideOnLoad); | |
600 | $(document).ready(addFormObserversForDoubleSubmit); |
|
601 | $(document).ready(addFormObserversForDoubleSubmit); |
@@ -1,1199 +1,1215 | |||||
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 | include Redmine::I18n |
|
21 | include Redmine::I18n | |
22 |
|
22 | |||
23 | fixtures :projects, :enabled_modules, :users, :members, |
|
23 | fixtures :projects, :enabled_modules, :users, :members, | |
24 | :member_roles, :roles, :trackers, :issue_statuses, |
|
24 | :member_roles, :roles, :trackers, :issue_statuses, | |
25 | :issue_categories, :enumerations, :issues, |
|
25 | :issue_categories, :enumerations, :issues, | |
26 | :watchers, :custom_fields, :custom_values, :versions, |
|
26 | :watchers, :custom_fields, :custom_values, :versions, | |
27 | :queries, |
|
27 | :queries, | |
28 | :projects_trackers, |
|
28 | :projects_trackers, | |
29 | :custom_fields_trackers |
|
29 | :custom_fields_trackers | |
30 |
|
30 | |||
31 | def test_custom_fields_for_all_projects_should_be_available_in_global_queries |
|
31 | def test_custom_fields_for_all_projects_should_be_available_in_global_queries | |
32 | query = Query.new(:project => nil, :name => '_') |
|
32 | query = Query.new(:project => nil, :name => '_') | |
33 | assert query.available_filters.has_key?('cf_1') |
|
33 | assert query.available_filters.has_key?('cf_1') | |
34 | assert !query.available_filters.has_key?('cf_3') |
|
34 | assert !query.available_filters.has_key?('cf_3') | |
35 | end |
|
35 | end | |
36 |
|
36 | |||
37 | def test_system_shared_versions_should_be_available_in_global_queries |
|
37 | def test_system_shared_versions_should_be_available_in_global_queries | |
38 | Version.find(2).update_attribute :sharing, 'system' |
|
38 | Version.find(2).update_attribute :sharing, 'system' | |
39 | query = Query.new(:project => nil, :name => '_') |
|
39 | query = Query.new(:project => nil, :name => '_') | |
40 | assert query.available_filters.has_key?('fixed_version_id') |
|
40 | assert query.available_filters.has_key?('fixed_version_id') | |
41 | assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'} |
|
41 | assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'} | |
42 | end |
|
42 | end | |
43 |
|
43 | |||
44 | def test_project_filter_in_global_queries |
|
44 | def test_project_filter_in_global_queries | |
45 | query = Query.new(:project => nil, :name => '_') |
|
45 | query = Query.new(:project => nil, :name => '_') | |
46 | project_filter = query.available_filters["project_id"] |
|
46 | project_filter = query.available_filters["project_id"] | |
47 | assert_not_nil project_filter |
|
47 | assert_not_nil project_filter | |
48 | project_ids = project_filter[:values].map{|p| p[1]} |
|
48 | project_ids = project_filter[:values].map{|p| p[1]} | |
49 | assert project_ids.include?("1") #public project |
|
49 | assert project_ids.include?("1") #public project | |
50 | assert !project_ids.include?("2") #private project user cannot see |
|
50 | assert !project_ids.include?("2") #private project user cannot see | |
51 | end |
|
51 | end | |
52 |
|
52 | |||
53 | def find_issues_with_query(query) |
|
53 | def find_issues_with_query(query) | |
54 | Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( |
|
54 | Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( | |
55 | query.statement |
|
55 | query.statement | |
56 | ).all |
|
56 | ).all | |
57 | end |
|
57 | end | |
58 |
|
58 | |||
59 | def assert_find_issues_with_query_is_successful(query) |
|
59 | def assert_find_issues_with_query_is_successful(query) | |
60 | assert_nothing_raised do |
|
60 | assert_nothing_raised do | |
61 | find_issues_with_query(query) |
|
61 | find_issues_with_query(query) | |
62 | end |
|
62 | end | |
63 | end |
|
63 | end | |
64 |
|
64 | |||
65 | def assert_query_statement_includes(query, condition) |
|
65 | def assert_query_statement_includes(query, condition) | |
66 | assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}" |
|
66 | assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}" | |
67 | end |
|
67 | end | |
68 |
|
68 | |||
69 | def assert_query_result(expected, query) |
|
69 | def assert_query_result(expected, query) | |
70 | assert_nothing_raised do |
|
70 | assert_nothing_raised do | |
71 | assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort |
|
71 | assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort | |
72 | assert_equal expected.size, query.issue_count |
|
72 | assert_equal expected.size, query.issue_count | |
73 | end |
|
73 | end | |
74 | end |
|
74 | end | |
75 |
|
75 | |||
76 | def test_query_should_allow_shared_versions_for_a_project_query |
|
76 | def test_query_should_allow_shared_versions_for_a_project_query | |
77 | subproject_version = Version.find(4) |
|
77 | subproject_version = Version.find(4) | |
78 | query = Query.new(:project => Project.find(1), :name => '_') |
|
78 | query = Query.new(:project => Project.find(1), :name => '_') | |
79 | query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s]) |
|
79 | query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s]) | |
80 |
|
80 | |||
81 | assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')") |
|
81 | assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')") | |
82 | end |
|
82 | end | |
83 |
|
83 | |||
84 | def test_query_with_multiple_custom_fields |
|
84 | def test_query_with_multiple_custom_fields | |
85 | query = Query.find(1) |
|
85 | query = Query.find(1) | |
86 | assert query.valid? |
|
86 | assert query.valid? | |
87 | assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')") |
|
87 | assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')") | |
88 | issues = find_issues_with_query(query) |
|
88 | issues = find_issues_with_query(query) | |
89 | assert_equal 1, issues.length |
|
89 | assert_equal 1, issues.length | |
90 | assert_equal Issue.find(3), issues.first |
|
90 | assert_equal Issue.find(3), issues.first | |
91 | end |
|
91 | end | |
92 |
|
92 | |||
93 | def test_operator_none |
|
93 | def test_operator_none | |
94 | query = Query.new(:project => Project.find(1), :name => '_') |
|
94 | query = Query.new(:project => Project.find(1), :name => '_') | |
95 | query.add_filter('fixed_version_id', '!*', ['']) |
|
95 | query.add_filter('fixed_version_id', '!*', ['']) | |
96 | query.add_filter('cf_1', '!*', ['']) |
|
96 | query.add_filter('cf_1', '!*', ['']) | |
97 | assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL") |
|
97 | assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL") | |
98 | assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''") |
|
98 | assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''") | |
99 | find_issues_with_query(query) |
|
99 | find_issues_with_query(query) | |
100 | end |
|
100 | end | |
101 |
|
101 | |||
102 | def test_operator_none_for_integer |
|
102 | def test_operator_none_for_integer | |
103 | query = Query.new(:project => Project.find(1), :name => '_') |
|
103 | query = Query.new(:project => Project.find(1), :name => '_') | |
104 | query.add_filter('estimated_hours', '!*', ['']) |
|
104 | query.add_filter('estimated_hours', '!*', ['']) | |
105 | issues = find_issues_with_query(query) |
|
105 | issues = find_issues_with_query(query) | |
106 | assert !issues.empty? |
|
106 | assert !issues.empty? | |
107 | assert issues.all? {|i| !i.estimated_hours} |
|
107 | assert issues.all? {|i| !i.estimated_hours} | |
108 | end |
|
108 | end | |
109 |
|
109 | |||
110 | def test_operator_none_for_date |
|
110 | def test_operator_none_for_date | |
111 | query = Query.new(:project => Project.find(1), :name => '_') |
|
111 | query = Query.new(:project => Project.find(1), :name => '_') | |
112 | query.add_filter('start_date', '!*', ['']) |
|
112 | query.add_filter('start_date', '!*', ['']) | |
113 | issues = find_issues_with_query(query) |
|
113 | issues = find_issues_with_query(query) | |
114 | assert !issues.empty? |
|
114 | assert !issues.empty? | |
115 | assert issues.all? {|i| i.start_date.nil?} |
|
115 | assert issues.all? {|i| i.start_date.nil?} | |
116 | end |
|
116 | end | |
117 |
|
117 | |||
118 | def test_operator_none_for_string_custom_field |
|
118 | def test_operator_none_for_string_custom_field | |
119 | query = Query.new(:project => Project.find(1), :name => '_') |
|
119 | query = Query.new(:project => Project.find(1), :name => '_') | |
120 | query.add_filter('cf_2', '!*', ['']) |
|
120 | query.add_filter('cf_2', '!*', ['']) | |
121 | assert query.has_filter?('cf_2') |
|
121 | assert query.has_filter?('cf_2') | |
122 | issues = find_issues_with_query(query) |
|
122 | issues = find_issues_with_query(query) | |
123 | assert !issues.empty? |
|
123 | assert !issues.empty? | |
124 | assert issues.all? {|i| i.custom_field_value(2).blank?} |
|
124 | assert issues.all? {|i| i.custom_field_value(2).blank?} | |
125 | end |
|
125 | end | |
126 |
|
126 | |||
127 | def test_operator_all |
|
127 | def test_operator_all | |
128 | query = Query.new(:project => Project.find(1), :name => '_') |
|
128 | query = Query.new(:project => Project.find(1), :name => '_') | |
129 | query.add_filter('fixed_version_id', '*', ['']) |
|
129 | query.add_filter('fixed_version_id', '*', ['']) | |
130 | query.add_filter('cf_1', '*', ['']) |
|
130 | query.add_filter('cf_1', '*', ['']) | |
131 | assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL") |
|
131 | assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL") | |
132 | assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''") |
|
132 | assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''") | |
133 | find_issues_with_query(query) |
|
133 | find_issues_with_query(query) | |
134 | end |
|
134 | end | |
135 |
|
135 | |||
136 | def test_operator_all_for_date |
|
136 | def test_operator_all_for_date | |
137 | query = Query.new(:project => Project.find(1), :name => '_') |
|
137 | query = Query.new(:project => Project.find(1), :name => '_') | |
138 | query.add_filter('start_date', '*', ['']) |
|
138 | query.add_filter('start_date', '*', ['']) | |
139 | issues = find_issues_with_query(query) |
|
139 | issues = find_issues_with_query(query) | |
140 | assert !issues.empty? |
|
140 | assert !issues.empty? | |
141 | assert issues.all? {|i| i.start_date.present?} |
|
141 | assert issues.all? {|i| i.start_date.present?} | |
142 | end |
|
142 | end | |
143 |
|
143 | |||
144 | def test_operator_all_for_string_custom_field |
|
144 | def test_operator_all_for_string_custom_field | |
145 | query = Query.new(:project => Project.find(1), :name => '_') |
|
145 | query = Query.new(:project => Project.find(1), :name => '_') | |
146 | query.add_filter('cf_2', '*', ['']) |
|
146 | query.add_filter('cf_2', '*', ['']) | |
147 | assert query.has_filter?('cf_2') |
|
147 | assert query.has_filter?('cf_2') | |
148 | issues = find_issues_with_query(query) |
|
148 | issues = find_issues_with_query(query) | |
149 | assert !issues.empty? |
|
149 | assert !issues.empty? | |
150 | assert issues.all? {|i| i.custom_field_value(2).present?} |
|
150 | assert issues.all? {|i| i.custom_field_value(2).present?} | |
151 | end |
|
151 | end | |
152 |
|
152 | |||
153 | def test_numeric_filter_should_not_accept_non_numeric_values |
|
153 | def test_numeric_filter_should_not_accept_non_numeric_values | |
154 | query = Query.new(:name => '_') |
|
154 | query = Query.new(:name => '_') | |
155 | query.add_filter('estimated_hours', '=', ['a']) |
|
155 | query.add_filter('estimated_hours', '=', ['a']) | |
156 |
|
156 | |||
157 | assert query.has_filter?('estimated_hours') |
|
157 | assert query.has_filter?('estimated_hours') | |
158 | assert !query.valid? |
|
158 | assert !query.valid? | |
159 | end |
|
159 | end | |
160 |
|
160 | |||
161 | def test_operator_is_on_float |
|
161 | def test_operator_is_on_float | |
162 | Issue.update_all("estimated_hours = 171.2", "id=2") |
|
162 | Issue.update_all("estimated_hours = 171.2", "id=2") | |
163 |
|
163 | |||
164 | query = Query.new(:name => '_') |
|
164 | query = Query.new(:name => '_') | |
165 | query.add_filter('estimated_hours', '=', ['171.20']) |
|
165 | query.add_filter('estimated_hours', '=', ['171.20']) | |
166 | issues = find_issues_with_query(query) |
|
166 | issues = find_issues_with_query(query) | |
167 | assert_equal 1, issues.size |
|
167 | assert_equal 1, issues.size | |
168 | assert_equal 2, issues.first.id |
|
168 | assert_equal 2, issues.first.id | |
169 | end |
|
169 | end | |
170 |
|
170 | |||
171 | def test_operator_is_on_integer_custom_field |
|
171 | def test_operator_is_on_integer_custom_field | |
172 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true) |
|
172 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true) | |
173 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') |
|
173 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') | |
174 | CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') |
|
174 | CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') | |
175 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') |
|
175 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') | |
176 |
|
176 | |||
177 | query = Query.new(:name => '_') |
|
177 | query = Query.new(:name => '_') | |
178 | query.add_filter("cf_#{f.id}", '=', ['12']) |
|
178 | query.add_filter("cf_#{f.id}", '=', ['12']) | |
179 | issues = find_issues_with_query(query) |
|
179 | issues = find_issues_with_query(query) | |
180 | assert_equal 1, issues.size |
|
180 | assert_equal 1, issues.size | |
181 | assert_equal 2, issues.first.id |
|
181 | assert_equal 2, issues.first.id | |
182 | end |
|
182 | end | |
183 |
|
183 | |||
184 | def test_operator_is_on_integer_custom_field_should_accept_negative_value |
|
184 | def test_operator_is_on_integer_custom_field_should_accept_negative_value | |
185 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true) |
|
185 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true) | |
186 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') |
|
186 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') | |
187 | CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12') |
|
187 | CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12') | |
188 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') |
|
188 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') | |
189 |
|
189 | |||
190 | query = Query.new(:name => '_') |
|
190 | query = Query.new(:name => '_') | |
191 | query.add_filter("cf_#{f.id}", '=', ['-12']) |
|
191 | query.add_filter("cf_#{f.id}", '=', ['-12']) | |
192 | assert query.valid? |
|
192 | assert query.valid? | |
193 | issues = find_issues_with_query(query) |
|
193 | issues = find_issues_with_query(query) | |
194 | assert_equal 1, issues.size |
|
194 | assert_equal 1, issues.size | |
195 | assert_equal 2, issues.first.id |
|
195 | assert_equal 2, issues.first.id | |
196 | end |
|
196 | end | |
197 |
|
197 | |||
198 | def test_operator_is_on_float_custom_field |
|
198 | def test_operator_is_on_float_custom_field | |
199 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true) |
|
199 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true) | |
200 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') |
|
200 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') | |
201 | CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7') |
|
201 | CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7') | |
202 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') |
|
202 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') | |
203 |
|
203 | |||
204 | query = Query.new(:name => '_') |
|
204 | query = Query.new(:name => '_') | |
205 | query.add_filter("cf_#{f.id}", '=', ['12.7']) |
|
205 | query.add_filter("cf_#{f.id}", '=', ['12.7']) | |
206 | issues = find_issues_with_query(query) |
|
206 | issues = find_issues_with_query(query) | |
207 | assert_equal 1, issues.size |
|
207 | assert_equal 1, issues.size | |
208 | assert_equal 2, issues.first.id |
|
208 | assert_equal 2, issues.first.id | |
209 | end |
|
209 | end | |
210 |
|
210 | |||
211 | def test_operator_is_on_float_custom_field_should_accept_negative_value |
|
211 | def test_operator_is_on_float_custom_field_should_accept_negative_value | |
212 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true) |
|
212 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true) | |
213 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') |
|
213 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') | |
214 | CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7') |
|
214 | CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7') | |
215 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') |
|
215 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') | |
216 |
|
216 | |||
217 | query = Query.new(:name => '_') |
|
217 | query = Query.new(:name => '_') | |
218 | query.add_filter("cf_#{f.id}", '=', ['-12.7']) |
|
218 | query.add_filter("cf_#{f.id}", '=', ['-12.7']) | |
219 | assert query.valid? |
|
219 | assert query.valid? | |
220 | issues = find_issues_with_query(query) |
|
220 | issues = find_issues_with_query(query) | |
221 | assert_equal 1, issues.size |
|
221 | assert_equal 1, issues.size | |
222 | assert_equal 2, issues.first.id |
|
222 | assert_equal 2, issues.first.id | |
223 | end |
|
223 | end | |
224 |
|
224 | |||
225 | def test_operator_is_on_multi_list_custom_field |
|
225 | def test_operator_is_on_multi_list_custom_field | |
226 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, |
|
226 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, | |
227 | :possible_values => ['value1', 'value2', 'value3'], :multiple => true) |
|
227 | :possible_values => ['value1', 'value2', 'value3'], :multiple => true) | |
228 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') |
|
228 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') | |
229 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') |
|
229 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') | |
230 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') |
|
230 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') | |
231 |
|
231 | |||
232 | query = Query.new(:name => '_') |
|
232 | query = Query.new(:name => '_') | |
233 | query.add_filter("cf_#{f.id}", '=', ['value1']) |
|
233 | query.add_filter("cf_#{f.id}", '=', ['value1']) | |
234 | issues = find_issues_with_query(query) |
|
234 | issues = find_issues_with_query(query) | |
235 | assert_equal [1, 3], issues.map(&:id).sort |
|
235 | assert_equal [1, 3], issues.map(&:id).sort | |
236 |
|
236 | |||
237 | query = Query.new(:name => '_') |
|
237 | query = Query.new(:name => '_') | |
238 | query.add_filter("cf_#{f.id}", '=', ['value2']) |
|
238 | query.add_filter("cf_#{f.id}", '=', ['value2']) | |
239 | issues = find_issues_with_query(query) |
|
239 | issues = find_issues_with_query(query) | |
240 | assert_equal [1], issues.map(&:id).sort |
|
240 | assert_equal [1], issues.map(&:id).sort | |
241 | end |
|
241 | end | |
242 |
|
242 | |||
243 | def test_operator_is_not_on_multi_list_custom_field |
|
243 | def test_operator_is_not_on_multi_list_custom_field | |
244 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, |
|
244 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, | |
245 | :possible_values => ['value1', 'value2', 'value3'], :multiple => true) |
|
245 | :possible_values => ['value1', 'value2', 'value3'], :multiple => true) | |
246 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') |
|
246 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') | |
247 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') |
|
247 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') | |
248 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') |
|
248 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') | |
249 |
|
249 | |||
250 | query = Query.new(:name => '_') |
|
250 | query = Query.new(:name => '_') | |
251 | query.add_filter("cf_#{f.id}", '!', ['value1']) |
|
251 | query.add_filter("cf_#{f.id}", '!', ['value1']) | |
252 | issues = find_issues_with_query(query) |
|
252 | issues = find_issues_with_query(query) | |
253 | assert !issues.map(&:id).include?(1) |
|
253 | assert !issues.map(&:id).include?(1) | |
254 | assert !issues.map(&:id).include?(3) |
|
254 | assert !issues.map(&:id).include?(3) | |
255 |
|
255 | |||
256 | query = Query.new(:name => '_') |
|
256 | query = Query.new(:name => '_') | |
257 | query.add_filter("cf_#{f.id}", '!', ['value2']) |
|
257 | query.add_filter("cf_#{f.id}", '!', ['value2']) | |
258 | issues = find_issues_with_query(query) |
|
258 | issues = find_issues_with_query(query) | |
259 | assert !issues.map(&:id).include?(1) |
|
259 | assert !issues.map(&:id).include?(1) | |
260 | assert issues.map(&:id).include?(3) |
|
260 | assert issues.map(&:id).include?(3) | |
261 | end |
|
261 | end | |
262 |
|
262 | |||
263 | def test_operator_is_on_is_private_field |
|
263 | def test_operator_is_on_is_private_field | |
264 | # is_private filter only available for those who can set issues private |
|
264 | # is_private filter only available for those who can set issues private | |
265 | User.current = User.find(2) |
|
265 | User.current = User.find(2) | |
266 |
|
266 | |||
267 | query = Query.new(:name => '_') |
|
267 | query = Query.new(:name => '_') | |
268 | assert query.available_filters.key?('is_private') |
|
268 | assert query.available_filters.key?('is_private') | |
269 |
|
269 | |||
270 | query.add_filter("is_private", '=', ['1']) |
|
270 | query.add_filter("is_private", '=', ['1']) | |
271 | issues = find_issues_with_query(query) |
|
271 | issues = find_issues_with_query(query) | |
272 | assert issues.any? |
|
272 | assert issues.any? | |
273 | assert_nil issues.detect {|issue| !issue.is_private?} |
|
273 | assert_nil issues.detect {|issue| !issue.is_private?} | |
274 | ensure |
|
274 | ensure | |
275 | User.current = nil |
|
275 | User.current = nil | |
276 | end |
|
276 | end | |
277 |
|
277 | |||
278 | def test_operator_is_not_on_is_private_field |
|
278 | def test_operator_is_not_on_is_private_field | |
279 | # is_private filter only available for those who can set issues private |
|
279 | # is_private filter only available for those who can set issues private | |
280 | User.current = User.find(2) |
|
280 | User.current = User.find(2) | |
281 |
|
281 | |||
282 | query = Query.new(:name => '_') |
|
282 | query = Query.new(:name => '_') | |
283 | assert query.available_filters.key?('is_private') |
|
283 | assert query.available_filters.key?('is_private') | |
284 |
|
284 | |||
285 | query.add_filter("is_private", '!', ['1']) |
|
285 | query.add_filter("is_private", '!', ['1']) | |
286 | issues = find_issues_with_query(query) |
|
286 | issues = find_issues_with_query(query) | |
287 | assert issues.any? |
|
287 | assert issues.any? | |
288 | assert_nil issues.detect {|issue| issue.is_private?} |
|
288 | assert_nil issues.detect {|issue| issue.is_private?} | |
289 | ensure |
|
289 | ensure | |
290 | User.current = nil |
|
290 | User.current = nil | |
291 | end |
|
291 | end | |
292 |
|
292 | |||
293 | def test_operator_greater_than |
|
293 | def test_operator_greater_than | |
294 | query = Query.new(:project => Project.find(1), :name => '_') |
|
294 | query = Query.new(:project => Project.find(1), :name => '_') | |
295 | query.add_filter('done_ratio', '>=', ['40']) |
|
295 | query.add_filter('done_ratio', '>=', ['40']) | |
296 | assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0") |
|
296 | assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0") | |
297 | find_issues_with_query(query) |
|
297 | find_issues_with_query(query) | |
298 | end |
|
298 | end | |
299 |
|
299 | |||
300 | def test_operator_greater_than_a_float |
|
300 | def test_operator_greater_than_a_float | |
301 | query = Query.new(:project => Project.find(1), :name => '_') |
|
301 | query = Query.new(:project => Project.find(1), :name => '_') | |
302 | query.add_filter('estimated_hours', '>=', ['40.5']) |
|
302 | query.add_filter('estimated_hours', '>=', ['40.5']) | |
303 | assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5") |
|
303 | assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5") | |
304 | find_issues_with_query(query) |
|
304 | find_issues_with_query(query) | |
305 | end |
|
305 | end | |
306 |
|
306 | |||
307 | def test_operator_greater_than_on_int_custom_field |
|
307 | def test_operator_greater_than_on_int_custom_field | |
308 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) |
|
308 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) | |
309 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') |
|
309 | CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') | |
310 | CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') |
|
310 | CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') | |
311 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') |
|
311 | CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') | |
312 |
|
312 | |||
313 | query = Query.new(:project => Project.find(1), :name => '_') |
|
313 | query = Query.new(:project => Project.find(1), :name => '_') | |
314 | query.add_filter("cf_#{f.id}", '>=', ['8']) |
|
314 | query.add_filter("cf_#{f.id}", '>=', ['8']) | |
315 | issues = find_issues_with_query(query) |
|
315 | issues = find_issues_with_query(query) | |
316 | assert_equal 1, issues.size |
|
316 | assert_equal 1, issues.size | |
317 | assert_equal 2, issues.first.id |
|
317 | assert_equal 2, issues.first.id | |
318 | end |
|
318 | end | |
319 |
|
319 | |||
320 | def test_operator_lesser_than |
|
320 | def test_operator_lesser_than | |
321 | query = Query.new(:project => Project.find(1), :name => '_') |
|
321 | query = Query.new(:project => Project.find(1), :name => '_') | |
322 | query.add_filter('done_ratio', '<=', ['30']) |
|
322 | query.add_filter('done_ratio', '<=', ['30']) | |
323 | assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0") |
|
323 | assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0") | |
324 | find_issues_with_query(query) |
|
324 | find_issues_with_query(query) | |
325 | end |
|
325 | end | |
326 |
|
326 | |||
327 | def test_operator_lesser_than_on_custom_field |
|
327 | def test_operator_lesser_than_on_custom_field | |
328 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) |
|
328 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) | |
329 | query = Query.new(:project => Project.find(1), :name => '_') |
|
329 | query = Query.new(:project => Project.find(1), :name => '_') | |
330 | query.add_filter("cf_#{f.id}", '<=', ['30']) |
|
330 | query.add_filter("cf_#{f.id}", '<=', ['30']) | |
331 | assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0") |
|
331 | assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0") | |
332 | find_issues_with_query(query) |
|
332 | find_issues_with_query(query) | |
333 | end |
|
333 | end | |
334 |
|
334 | |||
335 | def test_operator_between |
|
335 | def test_operator_between | |
336 | query = Query.new(:project => Project.find(1), :name => '_') |
|
336 | query = Query.new(:project => Project.find(1), :name => '_') | |
337 | query.add_filter('done_ratio', '><', ['30', '40']) |
|
337 | query.add_filter('done_ratio', '><', ['30', '40']) | |
338 | assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement |
|
338 | assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement | |
339 | find_issues_with_query(query) |
|
339 | find_issues_with_query(query) | |
340 | end |
|
340 | end | |
341 |
|
341 | |||
342 | def test_operator_between_on_custom_field |
|
342 | def test_operator_between_on_custom_field | |
343 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) |
|
343 | f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) | |
344 | query = Query.new(:project => Project.find(1), :name => '_') |
|
344 | query = Query.new(:project => Project.find(1), :name => '_') | |
345 | query.add_filter("cf_#{f.id}", '><', ['30', '40']) |
|
345 | query.add_filter("cf_#{f.id}", '><', ['30', '40']) | |
346 | assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement |
|
346 | assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement | |
347 | find_issues_with_query(query) |
|
347 | find_issues_with_query(query) | |
348 | end |
|
348 | end | |
349 |
|
349 | |||
350 | def test_date_filter_should_not_accept_non_date_values |
|
350 | def test_date_filter_should_not_accept_non_date_values | |
351 | query = Query.new(:name => '_') |
|
351 | query = Query.new(:name => '_') | |
352 | query.add_filter('created_on', '=', ['a']) |
|
352 | query.add_filter('created_on', '=', ['a']) | |
353 |
|
353 | |||
354 | assert query.has_filter?('created_on') |
|
354 | assert query.has_filter?('created_on') | |
355 | assert !query.valid? |
|
355 | assert !query.valid? | |
356 | end |
|
356 | end | |
357 |
|
357 | |||
358 | def test_date_filter_should_not_accept_invalid_date_values |
|
358 | def test_date_filter_should_not_accept_invalid_date_values | |
359 | query = Query.new(:name => '_') |
|
359 | query = Query.new(:name => '_') | |
360 | query.add_filter('created_on', '=', ['2011-01-34']) |
|
360 | query.add_filter('created_on', '=', ['2011-01-34']) | |
361 |
|
361 | |||
362 | assert query.has_filter?('created_on') |
|
362 | assert query.has_filter?('created_on') | |
363 | assert !query.valid? |
|
363 | assert !query.valid? | |
364 | end |
|
364 | end | |
365 |
|
365 | |||
366 | def test_relative_date_filter_should_not_accept_non_integer_values |
|
366 | def test_relative_date_filter_should_not_accept_non_integer_values | |
367 | query = Query.new(:name => '_') |
|
367 | query = Query.new(:name => '_') | |
368 | query.add_filter('created_on', '>t-', ['a']) |
|
368 | query.add_filter('created_on', '>t-', ['a']) | |
369 |
|
369 | |||
370 | assert query.has_filter?('created_on') |
|
370 | assert query.has_filter?('created_on') | |
371 | assert !query.valid? |
|
371 | assert !query.valid? | |
372 | end |
|
372 | end | |
373 |
|
373 | |||
374 | def test_operator_date_equals |
|
374 | def test_operator_date_equals | |
375 | query = Query.new(:name => '_') |
|
375 | query = Query.new(:name => '_') | |
376 | query.add_filter('due_date', '=', ['2011-07-10']) |
|
376 | query.add_filter('due_date', '=', ['2011-07-10']) | |
377 | 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 |
|
377 | 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 | |
378 | find_issues_with_query(query) |
|
378 | find_issues_with_query(query) | |
379 | end |
|
379 | end | |
380 |
|
380 | |||
381 | def test_operator_date_lesser_than |
|
381 | def test_operator_date_lesser_than | |
382 | query = Query.new(:name => '_') |
|
382 | query = Query.new(:name => '_') | |
383 | query.add_filter('due_date', '<=', ['2011-07-10']) |
|
383 | query.add_filter('due_date', '<=', ['2011-07-10']) | |
384 | assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement |
|
384 | assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement | |
385 | find_issues_with_query(query) |
|
385 | find_issues_with_query(query) | |
386 | end |
|
386 | end | |
387 |
|
387 | |||
388 | def test_operator_date_greater_than |
|
388 | def test_operator_date_greater_than | |
389 | query = Query.new(:name => '_') |
|
389 | query = Query.new(:name => '_') | |
390 | query.add_filter('due_date', '>=', ['2011-07-10']) |
|
390 | query.add_filter('due_date', '>=', ['2011-07-10']) | |
391 | assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement |
|
391 | assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement | |
392 | find_issues_with_query(query) |
|
392 | find_issues_with_query(query) | |
393 | end |
|
393 | end | |
394 |
|
394 | |||
395 | def test_operator_date_between |
|
395 | def test_operator_date_between | |
396 | query = Query.new(:name => '_') |
|
396 | query = Query.new(:name => '_') | |
397 | query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10']) |
|
397 | query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10']) | |
398 | 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 |
|
398 | 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 | |
399 | find_issues_with_query(query) |
|
399 | find_issues_with_query(query) | |
400 | end |
|
400 | end | |
401 |
|
401 | |||
402 | def test_operator_in_more_than |
|
402 | def test_operator_in_more_than | |
403 | Issue.find(7).update_attribute(:due_date, (Date.today + 15)) |
|
403 | Issue.find(7).update_attribute(:due_date, (Date.today + 15)) | |
404 | query = Query.new(:project => Project.find(1), :name => '_') |
|
404 | query = Query.new(:project => Project.find(1), :name => '_') | |
405 | query.add_filter('due_date', '>t+', ['15']) |
|
405 | query.add_filter('due_date', '>t+', ['15']) | |
406 | issues = find_issues_with_query(query) |
|
406 | issues = find_issues_with_query(query) | |
407 | assert !issues.empty? |
|
407 | assert !issues.empty? | |
408 | issues.each {|issue| assert(issue.due_date >= (Date.today + 15))} |
|
408 | issues.each {|issue| assert(issue.due_date >= (Date.today + 15))} | |
409 | end |
|
409 | end | |
410 |
|
410 | |||
411 | def test_operator_in_less_than |
|
411 | def test_operator_in_less_than | |
412 | query = Query.new(:project => Project.find(1), :name => '_') |
|
412 | query = Query.new(:project => Project.find(1), :name => '_') | |
413 | query.add_filter('due_date', '<t+', ['15']) |
|
413 | query.add_filter('due_date', '<t+', ['15']) | |
414 | issues = find_issues_with_query(query) |
|
414 | issues = find_issues_with_query(query) | |
415 | assert !issues.empty? |
|
415 | assert !issues.empty? | |
416 | issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))} |
|
416 | issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))} | |
417 | end |
|
417 | end | |
418 |
|
418 | |||
419 | def test_operator_less_than_ago |
|
419 | def test_operator_less_than_ago | |
420 | Issue.find(7).update_attribute(:due_date, (Date.today - 3)) |
|
420 | Issue.find(7).update_attribute(:due_date, (Date.today - 3)) | |
421 | query = Query.new(:project => Project.find(1), :name => '_') |
|
421 | query = Query.new(:project => Project.find(1), :name => '_') | |
422 | query.add_filter('due_date', '>t-', ['3']) |
|
422 | query.add_filter('due_date', '>t-', ['3']) | |
423 | issues = find_issues_with_query(query) |
|
423 | issues = find_issues_with_query(query) | |
424 | assert !issues.empty? |
|
424 | assert !issues.empty? | |
425 | issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)} |
|
425 | issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)} | |
426 | end |
|
426 | end | |
427 |
|
427 | |||
428 | def test_operator_more_than_ago |
|
428 | def test_operator_more_than_ago | |
429 | Issue.find(7).update_attribute(:due_date, (Date.today - 10)) |
|
429 | Issue.find(7).update_attribute(:due_date, (Date.today - 10)) | |
430 | query = Query.new(:project => Project.find(1), :name => '_') |
|
430 | query = Query.new(:project => Project.find(1), :name => '_') | |
431 | query.add_filter('due_date', '<t-', ['10']) |
|
431 | query.add_filter('due_date', '<t-', ['10']) | |
432 | assert query.statement.include?("#{Issue.table_name}.due_date <=") |
|
432 | assert query.statement.include?("#{Issue.table_name}.due_date <=") | |
433 | issues = find_issues_with_query(query) |
|
433 | issues = find_issues_with_query(query) | |
434 | assert !issues.empty? |
|
434 | assert !issues.empty? | |
435 | issues.each {|issue| assert(issue.due_date <= (Date.today - 10))} |
|
435 | issues.each {|issue| assert(issue.due_date <= (Date.today - 10))} | |
436 | end |
|
436 | end | |
437 |
|
437 | |||
438 | def test_operator_in |
|
438 | def test_operator_in | |
439 | Issue.find(7).update_attribute(:due_date, (Date.today + 2)) |
|
439 | Issue.find(7).update_attribute(:due_date, (Date.today + 2)) | |
440 | query = Query.new(:project => Project.find(1), :name => '_') |
|
440 | query = Query.new(:project => Project.find(1), :name => '_') | |
441 | query.add_filter('due_date', 't+', ['2']) |
|
441 | query.add_filter('due_date', 't+', ['2']) | |
442 | issues = find_issues_with_query(query) |
|
442 | issues = find_issues_with_query(query) | |
443 | assert !issues.empty? |
|
443 | assert !issues.empty? | |
444 | issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)} |
|
444 | issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)} | |
445 | end |
|
445 | end | |
446 |
|
446 | |||
447 | def test_operator_ago |
|
447 | def test_operator_ago | |
448 | Issue.find(7).update_attribute(:due_date, (Date.today - 3)) |
|
448 | Issue.find(7).update_attribute(:due_date, (Date.today - 3)) | |
449 | query = Query.new(:project => Project.find(1), :name => '_') |
|
449 | query = Query.new(:project => Project.find(1), :name => '_') | |
450 | query.add_filter('due_date', 't-', ['3']) |
|
450 | query.add_filter('due_date', 't-', ['3']) | |
451 | issues = find_issues_with_query(query) |
|
451 | issues = find_issues_with_query(query) | |
452 | assert !issues.empty? |
|
452 | assert !issues.empty? | |
453 | issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)} |
|
453 | issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)} | |
454 | end |
|
454 | end | |
455 |
|
455 | |||
456 | def test_operator_today |
|
456 | def test_operator_today | |
457 | query = Query.new(:project => Project.find(1), :name => '_') |
|
457 | query = Query.new(:project => Project.find(1), :name => '_') | |
458 | query.add_filter('due_date', 't', ['']) |
|
458 | query.add_filter('due_date', 't', ['']) | |
459 | issues = find_issues_with_query(query) |
|
459 | issues = find_issues_with_query(query) | |
460 | assert !issues.empty? |
|
460 | assert !issues.empty? | |
461 | issues.each {|issue| assert_equal Date.today, issue.due_date} |
|
461 | issues.each {|issue| assert_equal Date.today, issue.due_date} | |
462 | end |
|
462 | end | |
463 |
|
463 | |||
464 | def test_operator_this_week_on_date |
|
464 | def test_operator_this_week_on_date | |
465 | query = Query.new(:project => Project.find(1), :name => '_') |
|
465 | query = Query.new(:project => Project.find(1), :name => '_') | |
466 | query.add_filter('due_date', 'w', ['']) |
|
466 | query.add_filter('due_date', 'w', ['']) | |
467 | find_issues_with_query(query) |
|
467 | find_issues_with_query(query) | |
468 | end |
|
468 | end | |
469 |
|
469 | |||
470 | def test_operator_this_week_on_datetime |
|
470 | def test_operator_this_week_on_datetime | |
471 | query = Query.new(:project => Project.find(1), :name => '_') |
|
471 | query = Query.new(:project => Project.find(1), :name => '_') | |
472 | query.add_filter('created_on', 'w', ['']) |
|
472 | query.add_filter('created_on', 'w', ['']) | |
473 | find_issues_with_query(query) |
|
473 | find_issues_with_query(query) | |
474 | end |
|
474 | end | |
475 |
|
475 | |||
476 | def test_operator_contains |
|
476 | def test_operator_contains | |
477 | query = Query.new(:project => Project.find(1), :name => '_') |
|
477 | query = Query.new(:project => Project.find(1), :name => '_') | |
478 | query.add_filter('subject', '~', ['uNable']) |
|
478 | query.add_filter('subject', '~', ['uNable']) | |
479 | assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'") |
|
479 | assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'") | |
480 | result = find_issues_with_query(query) |
|
480 | result = find_issues_with_query(query) | |
481 | assert result.empty? |
|
481 | assert result.empty? | |
482 | result.each {|issue| assert issue.subject.downcase.include?('unable') } |
|
482 | result.each {|issue| assert issue.subject.downcase.include?('unable') } | |
483 | end |
|
483 | end | |
484 |
|
484 | |||
485 | def test_range_for_this_week_with_week_starting_on_monday |
|
485 | def test_range_for_this_week_with_week_starting_on_monday | |
486 | I18n.locale = :fr |
|
486 | I18n.locale = :fr | |
487 | assert_equal '1', I18n.t(:general_first_day_of_week) |
|
487 | assert_equal '1', I18n.t(:general_first_day_of_week) | |
488 |
|
488 | |||
489 | Date.stubs(:today).returns(Date.parse('2011-04-29')) |
|
489 | Date.stubs(:today).returns(Date.parse('2011-04-29')) | |
490 |
|
490 | |||
491 | query = Query.new(:project => Project.find(1), :name => '_') |
|
491 | query = Query.new(:project => Project.find(1), :name => '_') | |
492 | query.add_filter('due_date', 'w', ['']) |
|
492 | query.add_filter('due_date', 'w', ['']) | |
493 | 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}" |
|
493 | 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}" | |
494 | I18n.locale = :en |
|
494 | I18n.locale = :en | |
495 | end |
|
495 | end | |
496 |
|
496 | |||
497 | def test_range_for_this_week_with_week_starting_on_sunday |
|
497 | def test_range_for_this_week_with_week_starting_on_sunday | |
498 | I18n.locale = :en |
|
498 | I18n.locale = :en | |
499 | assert_equal '7', I18n.t(:general_first_day_of_week) |
|
499 | assert_equal '7', I18n.t(:general_first_day_of_week) | |
500 |
|
500 | |||
501 | Date.stubs(:today).returns(Date.parse('2011-04-29')) |
|
501 | Date.stubs(:today).returns(Date.parse('2011-04-29')) | |
502 |
|
502 | |||
503 | query = Query.new(:project => Project.find(1), :name => '_') |
|
503 | query = Query.new(:project => Project.find(1), :name => '_') | |
504 | query.add_filter('due_date', 'w', ['']) |
|
504 | query.add_filter('due_date', 'w', ['']) | |
505 | 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}" |
|
505 | 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}" | |
506 | end |
|
506 | end | |
507 |
|
507 | |||
508 | def test_operator_does_not_contains |
|
508 | def test_operator_does_not_contains | |
509 | query = Query.new(:project => Project.find(1), :name => '_') |
|
509 | query = Query.new(:project => Project.find(1), :name => '_') | |
510 | query.add_filter('subject', '!~', ['uNable']) |
|
510 | query.add_filter('subject', '!~', ['uNable']) | |
511 | assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'") |
|
511 | assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'") | |
512 | find_issues_with_query(query) |
|
512 | find_issues_with_query(query) | |
513 | end |
|
513 | end | |
514 |
|
514 | |||
515 | def test_filter_assigned_to_me |
|
515 | def test_filter_assigned_to_me | |
516 | user = User.find(2) |
|
516 | user = User.find(2) | |
517 | group = Group.find(10) |
|
517 | group = Group.find(10) | |
518 | User.current = user |
|
518 | User.current = user | |
519 | i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user) |
|
519 | i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user) | |
520 | i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group) |
|
520 | i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group) | |
521 | i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11)) |
|
521 | i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11)) | |
522 | group.users << user |
|
522 | group.users << user | |
523 |
|
523 | |||
524 | query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}}) |
|
524 | query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}}) | |
525 | result = query.issues |
|
525 | result = query.issues | |
526 | assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id) |
|
526 | assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id) | |
527 |
|
527 | |||
528 | assert result.include?(i1) |
|
528 | assert result.include?(i1) | |
529 | assert result.include?(i2) |
|
529 | assert result.include?(i2) | |
530 | assert !result.include?(i3) |
|
530 | assert !result.include?(i3) | |
531 | end |
|
531 | end | |
532 |
|
532 | |||
533 | def test_user_custom_field_filtered_on_me |
|
533 | def test_user_custom_field_filtered_on_me | |
534 | User.current = User.find(2) |
|
534 | User.current = User.find(2) | |
535 | cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1]) |
|
535 | cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1]) | |
536 | issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1) |
|
536 | issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1) | |
537 | issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'}) |
|
537 | issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'}) | |
538 |
|
538 | |||
539 | query = Query.new(:name => '_', :project => Project.find(1)) |
|
539 | query = Query.new(:name => '_', :project => Project.find(1)) | |
540 | filter = query.available_filters["cf_#{cf.id}"] |
|
540 | filter = query.available_filters["cf_#{cf.id}"] | |
541 | assert_not_nil filter |
|
541 | assert_not_nil filter | |
542 | assert_include 'me', filter[:values].map{|v| v[1]} |
|
542 | assert_include 'me', filter[:values].map{|v| v[1]} | |
543 |
|
543 | |||
544 | query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}} |
|
544 | query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}} | |
545 | result = query.issues |
|
545 | result = query.issues | |
546 | assert_equal 1, result.size |
|
546 | assert_equal 1, result.size | |
547 | assert_equal issue1, result.first |
|
547 | assert_equal issue1, result.first | |
548 | end |
|
548 | end | |
549 |
|
549 | |||
550 | def test_filter_my_projects |
|
550 | def test_filter_my_projects | |
551 | User.current = User.find(2) |
|
551 | User.current = User.find(2) | |
552 | query = Query.new(:name => '_') |
|
552 | query = Query.new(:name => '_') | |
553 | filter = query.available_filters['project_id'] |
|
553 | filter = query.available_filters['project_id'] | |
554 | assert_not_nil filter |
|
554 | assert_not_nil filter | |
555 | assert_include 'mine', filter[:values].map{|v| v[1]} |
|
555 | assert_include 'mine', filter[:values].map{|v| v[1]} | |
556 |
|
556 | |||
557 | query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}} |
|
557 | query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}} | |
558 | result = query.issues |
|
558 | result = query.issues | |
559 | assert_nil result.detect {|issue| !User.current.member_of?(issue.project)} |
|
559 | assert_nil result.detect {|issue| !User.current.member_of?(issue.project)} | |
560 | end |
|
560 | end | |
561 |
|
561 | |||
562 | def test_filter_watched_issues |
|
562 | def test_filter_watched_issues | |
563 | User.current = User.find(1) |
|
563 | User.current = User.find(1) | |
564 | query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}}) |
|
564 | query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}}) | |
565 | result = find_issues_with_query(query) |
|
565 | result = find_issues_with_query(query) | |
566 | assert_not_nil result |
|
566 | assert_not_nil result | |
567 | assert !result.empty? |
|
567 | assert !result.empty? | |
568 | assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id) |
|
568 | assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id) | |
569 | User.current = nil |
|
569 | User.current = nil | |
570 | end |
|
570 | end | |
571 |
|
571 | |||
572 | def test_filter_unwatched_issues |
|
572 | def test_filter_unwatched_issues | |
573 | User.current = User.find(1) |
|
573 | User.current = User.find(1) | |
574 | query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}}) |
|
574 | query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}}) | |
575 | result = find_issues_with_query(query) |
|
575 | result = find_issues_with_query(query) | |
576 | assert_not_nil result |
|
576 | assert_not_nil result | |
577 | assert !result.empty? |
|
577 | assert !result.empty? | |
578 | assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size) |
|
578 | assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size) | |
579 | User.current = nil |
|
579 | User.current = nil | |
580 | end |
|
580 | end | |
581 |
|
581 | |||
582 | def test_filter_on_project_custom_field |
|
582 | def test_filter_on_project_custom_field | |
583 | field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') |
|
583 | field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') | |
584 | CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo') |
|
584 | CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo') | |
585 | CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo') |
|
585 | CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo') | |
586 |
|
586 | |||
587 | query = Query.new(:name => '_') |
|
587 | query = Query.new(:name => '_') | |
588 | filter_name = "project.cf_#{field.id}" |
|
588 | filter_name = "project.cf_#{field.id}" | |
589 | assert_include filter_name, query.available_filters.keys |
|
589 | assert_include filter_name, query.available_filters.keys | |
590 | query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} |
|
590 | query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} | |
591 | assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort |
|
591 | assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort | |
592 | end |
|
592 | end | |
593 |
|
593 | |||
594 | def test_filter_on_author_custom_field |
|
594 | def test_filter_on_author_custom_field | |
595 | field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') |
|
595 | field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') | |
596 | CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') |
|
596 | CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') | |
597 |
|
597 | |||
598 | query = Query.new(:name => '_') |
|
598 | query = Query.new(:name => '_') | |
599 | filter_name = "author.cf_#{field.id}" |
|
599 | filter_name = "author.cf_#{field.id}" | |
600 | assert_include filter_name, query.available_filters.keys |
|
600 | assert_include filter_name, query.available_filters.keys | |
601 | query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} |
|
601 | query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} | |
602 | assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort |
|
602 | assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort | |
603 | end |
|
603 | end | |
604 |
|
604 | |||
605 | def test_filter_on_assigned_to_custom_field |
|
605 | def test_filter_on_assigned_to_custom_field | |
606 | field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') |
|
606 | field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') | |
607 | CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') |
|
607 | CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') | |
608 |
|
608 | |||
609 | query = Query.new(:name => '_') |
|
609 | query = Query.new(:name => '_') | |
610 | filter_name = "assigned_to.cf_#{field.id}" |
|
610 | filter_name = "assigned_to.cf_#{field.id}" | |
611 | assert_include filter_name, query.available_filters.keys |
|
611 | assert_include filter_name, query.available_filters.keys | |
612 | query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} |
|
612 | query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} | |
613 | assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort |
|
613 | assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort | |
614 | end |
|
614 | end | |
615 |
|
615 | |||
616 | def test_filter_on_fixed_version_custom_field |
|
616 | def test_filter_on_fixed_version_custom_field | |
617 | field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') |
|
617 | field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') | |
618 | CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo') |
|
618 | CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo') | |
619 |
|
619 | |||
620 | query = Query.new(:name => '_') |
|
620 | query = Query.new(:name => '_') | |
621 | filter_name = "fixed_version.cf_#{field.id}" |
|
621 | filter_name = "fixed_version.cf_#{field.id}" | |
622 | assert_include filter_name, query.available_filters.keys |
|
622 | assert_include filter_name, query.available_filters.keys | |
623 | query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} |
|
623 | query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} | |
624 | assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort |
|
624 | assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort | |
625 | end |
|
625 | end | |
626 |
|
626 | |||
627 | def test_filter_on_relations_with_a_specific_issue |
|
627 | def test_filter_on_relations_with_a_specific_issue | |
628 | IssueRelation.delete_all |
|
628 | IssueRelation.delete_all | |
629 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) |
|
629 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) | |
630 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) |
|
630 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) | |
631 |
|
631 | |||
632 | query = Query.new(:name => '_') |
|
632 | query = Query.new(:name => '_') | |
633 | query.filters = {"relates" => {:operator => '=', :values => ['1']}} |
|
633 | query.filters = {"relates" => {:operator => '=', :values => ['1']}} | |
634 | assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort |
|
634 | assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort | |
635 |
|
635 | |||
636 | query = Query.new(:name => '_') |
|
636 | query = Query.new(:name => '_') | |
637 | query.filters = {"relates" => {:operator => '=', :values => ['2']}} |
|
637 | query.filters = {"relates" => {:operator => '=', :values => ['2']}} | |
638 | assert_equal [1], find_issues_with_query(query).map(&:id).sort |
|
638 | assert_equal [1], find_issues_with_query(query).map(&:id).sort | |
639 | end |
|
639 | end | |
640 |
|
640 | |||
641 | def test_filter_on_relations_with_any_issues_in_a_project |
|
641 | def test_filter_on_relations_with_any_issues_in_a_project | |
642 | IssueRelation.delete_all |
|
642 | IssueRelation.delete_all | |
643 | with_settings :cross_project_issue_relations => '1' do |
|
643 | with_settings :cross_project_issue_relations => '1' do | |
644 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) |
|
644 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) | |
645 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first) |
|
645 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first) | |
646 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) |
|
646 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) | |
647 | end |
|
647 | end | |
648 |
|
648 | |||
649 | query = Query.new(:name => '_') |
|
649 | query = Query.new(:name => '_') | |
650 | query.filters = {"relates" => {:operator => '=p', :values => ['2']}} |
|
650 | query.filters = {"relates" => {:operator => '=p', :values => ['2']}} | |
651 | assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort |
|
651 | assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort | |
652 |
|
652 | |||
653 | query = Query.new(:name => '_') |
|
653 | query = Query.new(:name => '_') | |
654 | query.filters = {"relates" => {:operator => '=p', :values => ['3']}} |
|
654 | query.filters = {"relates" => {:operator => '=p', :values => ['3']}} | |
655 | assert_equal [1], find_issues_with_query(query).map(&:id).sort |
|
655 | assert_equal [1], find_issues_with_query(query).map(&:id).sort | |
656 |
|
656 | |||
657 | query = Query.new(:name => '_') |
|
657 | query = Query.new(:name => '_') | |
658 | query.filters = {"relates" => {:operator => '=p', :values => ['4']}} |
|
658 | query.filters = {"relates" => {:operator => '=p', :values => ['4']}} | |
659 | assert_equal [], find_issues_with_query(query).map(&:id).sort |
|
659 | assert_equal [], find_issues_with_query(query).map(&:id).sort | |
660 | end |
|
660 | end | |
661 |
|
661 | |||
662 | def test_filter_on_relations_with_any_issues_not_in_a_project |
|
662 | def test_filter_on_relations_with_any_issues_not_in_a_project | |
663 | IssueRelation.delete_all |
|
663 | IssueRelation.delete_all | |
664 | with_settings :cross_project_issue_relations => '1' do |
|
664 | with_settings :cross_project_issue_relations => '1' do | |
665 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) |
|
665 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) | |
666 | #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first) |
|
666 | #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first) | |
667 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) |
|
667 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) | |
668 | end |
|
668 | end | |
669 |
|
669 | |||
670 | query = Query.new(:name => '_') |
|
670 | query = Query.new(:name => '_') | |
671 | query.filters = {"relates" => {:operator => '=!p', :values => ['1']}} |
|
671 | query.filters = {"relates" => {:operator => '=!p', :values => ['1']}} | |
672 | assert_equal [1], find_issues_with_query(query).map(&:id).sort |
|
672 | assert_equal [1], find_issues_with_query(query).map(&:id).sort | |
673 | end |
|
673 | end | |
674 |
|
674 | |||
|
675 | def test_filter_on_relations_with_no_issues_in_a_project | |||
|
676 | IssueRelation.delete_all | |||
|
677 | with_settings :cross_project_issue_relations => '1' do | |||
|
678 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) | |||
|
679 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first) | |||
|
680 | IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3)) | |||
|
681 | end | |||
|
682 | ||||
|
683 | query = Query.new(:name => '_') | |||
|
684 | query.filters = {"relates" => {:operator => '!p', :values => ['2']}} | |||
|
685 | ids = find_issues_with_query(query).map(&:id).sort | |||
|
686 | assert_include 2, ids | |||
|
687 | assert_not_include 1, ids | |||
|
688 | assert_not_include 3, ids | |||
|
689 | end | |||
|
690 | ||||
675 | def test_filter_on_relations_with_no_issues |
|
691 | def test_filter_on_relations_with_no_issues | |
676 | IssueRelation.delete_all |
|
692 | IssueRelation.delete_all | |
677 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) |
|
693 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) | |
678 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) |
|
694 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) | |
679 |
|
695 | |||
680 | query = Query.new(:name => '_') |
|
696 | query = Query.new(:name => '_') | |
681 | query.filters = {"relates" => {:operator => '!*', :values => ['']}} |
|
697 | query.filters = {"relates" => {:operator => '!*', :values => ['']}} | |
682 | ids = find_issues_with_query(query).map(&:id) |
|
698 | ids = find_issues_with_query(query).map(&:id) | |
683 | assert_equal [], ids & [1, 2, 3] |
|
699 | assert_equal [], ids & [1, 2, 3] | |
684 | assert_include 4, ids |
|
700 | assert_include 4, ids | |
685 | end |
|
701 | end | |
686 |
|
702 | |||
687 | def test_filter_on_relations_with_any_issues |
|
703 | def test_filter_on_relations_with_any_issues | |
688 | IssueRelation.delete_all |
|
704 | IssueRelation.delete_all | |
689 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) |
|
705 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) | |
690 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) |
|
706 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) | |
691 |
|
707 | |||
692 | query = Query.new(:name => '_') |
|
708 | query = Query.new(:name => '_') | |
693 | query.filters = {"relates" => {:operator => '*', :values => ['']}} |
|
709 | query.filters = {"relates" => {:operator => '*', :values => ['']}} | |
694 | assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort |
|
710 | assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort | |
695 | end |
|
711 | end | |
696 |
|
712 | |||
697 | def test_statement_should_be_nil_with_no_filters |
|
713 | def test_statement_should_be_nil_with_no_filters | |
698 | q = Query.new(:name => '_') |
|
714 | q = Query.new(:name => '_') | |
699 | q.filters = {} |
|
715 | q.filters = {} | |
700 |
|
716 | |||
701 | assert q.valid? |
|
717 | assert q.valid? | |
702 | assert_nil q.statement |
|
718 | assert_nil q.statement | |
703 | end |
|
719 | end | |
704 |
|
720 | |||
705 | def test_default_columns |
|
721 | def test_default_columns | |
706 | q = Query.new |
|
722 | q = Query.new | |
707 | assert !q.columns.empty? |
|
723 | assert !q.columns.empty? | |
708 | end |
|
724 | end | |
709 |
|
725 | |||
710 | def test_set_column_names |
|
726 | def test_set_column_names | |
711 | q = Query.new |
|
727 | q = Query.new | |
712 | q.column_names = ['tracker', :subject, '', 'unknonw_column'] |
|
728 | q.column_names = ['tracker', :subject, '', 'unknonw_column'] | |
713 | assert_equal [:tracker, :subject], q.columns.collect {|c| c.name} |
|
729 | assert_equal [:tracker, :subject], q.columns.collect {|c| c.name} | |
714 | c = q.columns.first |
|
730 | c = q.columns.first | |
715 | assert q.has_column?(c) |
|
731 | assert q.has_column?(c) | |
716 | end |
|
732 | end | |
717 |
|
733 | |||
718 | def test_query_should_preload_spent_hours |
|
734 | def test_query_should_preload_spent_hours | |
719 | q = Query.new(:name => '_', :column_names => [:subject, :spent_hours]) |
|
735 | q = Query.new(:name => '_', :column_names => [:subject, :spent_hours]) | |
720 | assert q.has_column?(:spent_hours) |
|
736 | assert q.has_column?(:spent_hours) | |
721 | issues = q.issues |
|
737 | issues = q.issues | |
722 | assert_not_nil issues.first.instance_variable_get("@spent_hours") |
|
738 | assert_not_nil issues.first.instance_variable_get("@spent_hours") | |
723 | end |
|
739 | end | |
724 |
|
740 | |||
725 | def test_groupable_columns_should_include_custom_fields |
|
741 | def test_groupable_columns_should_include_custom_fields | |
726 | q = Query.new |
|
742 | q = Query.new | |
727 | column = q.groupable_columns.detect {|c| c.name == :cf_1} |
|
743 | column = q.groupable_columns.detect {|c| c.name == :cf_1} | |
728 | assert_not_nil column |
|
744 | assert_not_nil column | |
729 | assert_kind_of QueryCustomFieldColumn, column |
|
745 | assert_kind_of QueryCustomFieldColumn, column | |
730 | end |
|
746 | end | |
731 |
|
747 | |||
732 | def test_groupable_columns_should_not_include_multi_custom_fields |
|
748 | def test_groupable_columns_should_not_include_multi_custom_fields | |
733 | field = CustomField.find(1) |
|
749 | field = CustomField.find(1) | |
734 | field.update_attribute :multiple, true |
|
750 | field.update_attribute :multiple, true | |
735 |
|
751 | |||
736 | q = Query.new |
|
752 | q = Query.new | |
737 | column = q.groupable_columns.detect {|c| c.name == :cf_1} |
|
753 | column = q.groupable_columns.detect {|c| c.name == :cf_1} | |
738 | assert_nil column |
|
754 | assert_nil column | |
739 | end |
|
755 | end | |
740 |
|
756 | |||
741 | def test_groupable_columns_should_include_user_custom_fields |
|
757 | def test_groupable_columns_should_include_user_custom_fields | |
742 | cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user') |
|
758 | cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user') | |
743 |
|
759 | |||
744 | q = Query.new |
|
760 | q = Query.new | |
745 | assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym} |
|
761 | assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym} | |
746 | end |
|
762 | end | |
747 |
|
763 | |||
748 | def test_groupable_columns_should_include_version_custom_fields |
|
764 | def test_groupable_columns_should_include_version_custom_fields | |
749 | cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version') |
|
765 | cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version') | |
750 |
|
766 | |||
751 | q = Query.new |
|
767 | q = Query.new | |
752 | assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym} |
|
768 | assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym} | |
753 | end |
|
769 | end | |
754 |
|
770 | |||
755 | def test_grouped_with_valid_column |
|
771 | def test_grouped_with_valid_column | |
756 | q = Query.new(:group_by => 'status') |
|
772 | q = Query.new(:group_by => 'status') | |
757 | assert q.grouped? |
|
773 | assert q.grouped? | |
758 | assert_not_nil q.group_by_column |
|
774 | assert_not_nil q.group_by_column | |
759 | assert_equal :status, q.group_by_column.name |
|
775 | assert_equal :status, q.group_by_column.name | |
760 | assert_not_nil q.group_by_statement |
|
776 | assert_not_nil q.group_by_statement | |
761 | assert_equal 'status', q.group_by_statement |
|
777 | assert_equal 'status', q.group_by_statement | |
762 | end |
|
778 | end | |
763 |
|
779 | |||
764 | def test_grouped_with_invalid_column |
|
780 | def test_grouped_with_invalid_column | |
765 | q = Query.new(:group_by => 'foo') |
|
781 | q = Query.new(:group_by => 'foo') | |
766 | assert !q.grouped? |
|
782 | assert !q.grouped? | |
767 | assert_nil q.group_by_column |
|
783 | assert_nil q.group_by_column | |
768 | assert_nil q.group_by_statement |
|
784 | assert_nil q.group_by_statement | |
769 | end |
|
785 | end | |
770 |
|
786 | |||
771 | def test_sortable_columns_should_sort_assignees_according_to_user_format_setting |
|
787 | def test_sortable_columns_should_sort_assignees_according_to_user_format_setting | |
772 | with_settings :user_format => 'lastname_coma_firstname' do |
|
788 | with_settings :user_format => 'lastname_coma_firstname' do | |
773 | q = Query.new |
|
789 | q = Query.new | |
774 | assert q.sortable_columns.has_key?('assigned_to') |
|
790 | assert q.sortable_columns.has_key?('assigned_to') | |
775 | assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to'] |
|
791 | assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to'] | |
776 | end |
|
792 | end | |
777 | end |
|
793 | end | |
778 |
|
794 | |||
779 | def test_sortable_columns_should_sort_authors_according_to_user_format_setting |
|
795 | def test_sortable_columns_should_sort_authors_according_to_user_format_setting | |
780 | with_settings :user_format => 'lastname_coma_firstname' do |
|
796 | with_settings :user_format => 'lastname_coma_firstname' do | |
781 | q = Query.new |
|
797 | q = Query.new | |
782 | assert q.sortable_columns.has_key?('author') |
|
798 | assert q.sortable_columns.has_key?('author') | |
783 | assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author'] |
|
799 | assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author'] | |
784 | end |
|
800 | end | |
785 | end |
|
801 | end | |
786 |
|
802 | |||
787 | def test_sortable_columns_should_include_custom_field |
|
803 | def test_sortable_columns_should_include_custom_field | |
788 | q = Query.new |
|
804 | q = Query.new | |
789 | assert q.sortable_columns['cf_1'] |
|
805 | assert q.sortable_columns['cf_1'] | |
790 | end |
|
806 | end | |
791 |
|
807 | |||
792 | def test_sortable_columns_should_not_include_multi_custom_field |
|
808 | def test_sortable_columns_should_not_include_multi_custom_field | |
793 | field = CustomField.find(1) |
|
809 | field = CustomField.find(1) | |
794 | field.update_attribute :multiple, true |
|
810 | field.update_attribute :multiple, true | |
795 |
|
811 | |||
796 | q = Query.new |
|
812 | q = Query.new | |
797 | assert !q.sortable_columns['cf_1'] |
|
813 | assert !q.sortable_columns['cf_1'] | |
798 | end |
|
814 | end | |
799 |
|
815 | |||
800 | def test_default_sort |
|
816 | def test_default_sort | |
801 | q = Query.new |
|
817 | q = Query.new | |
802 | assert_equal [], q.sort_criteria |
|
818 | assert_equal [], q.sort_criteria | |
803 | end |
|
819 | end | |
804 |
|
820 | |||
805 | def test_set_sort_criteria_with_hash |
|
821 | def test_set_sort_criteria_with_hash | |
806 | q = Query.new |
|
822 | q = Query.new | |
807 | q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']} |
|
823 | q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']} | |
808 | assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria |
|
824 | assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria | |
809 | end |
|
825 | end | |
810 |
|
826 | |||
811 | def test_set_sort_criteria_with_array |
|
827 | def test_set_sort_criteria_with_array | |
812 | q = Query.new |
|
828 | q = Query.new | |
813 | q.sort_criteria = [['priority', 'desc'], 'tracker'] |
|
829 | q.sort_criteria = [['priority', 'desc'], 'tracker'] | |
814 | assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria |
|
830 | assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria | |
815 | end |
|
831 | end | |
816 |
|
832 | |||
817 | def test_create_query_with_sort |
|
833 | def test_create_query_with_sort | |
818 | q = Query.new(:name => 'Sorted') |
|
834 | q = Query.new(:name => 'Sorted') | |
819 | q.sort_criteria = [['priority', 'desc'], 'tracker'] |
|
835 | q.sort_criteria = [['priority', 'desc'], 'tracker'] | |
820 | assert q.save |
|
836 | assert q.save | |
821 | q.reload |
|
837 | q.reload | |
822 | assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria |
|
838 | assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria | |
823 | end |
|
839 | end | |
824 |
|
840 | |||
825 | def test_sort_by_string_custom_field_asc |
|
841 | def test_sort_by_string_custom_field_asc | |
826 | q = Query.new |
|
842 | q = Query.new | |
827 | c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } |
|
843 | c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } | |
828 | assert c |
|
844 | assert c | |
829 | assert c.sortable |
|
845 | assert c.sortable | |
830 | issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( |
|
846 | issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( | |
831 | q.statement |
|
847 | q.statement | |
832 | ).order("#{c.sortable} ASC").all |
|
848 | ).order("#{c.sortable} ASC").all | |
833 | values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s} |
|
849 | values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s} | |
834 | assert !values.empty? |
|
850 | assert !values.empty? | |
835 | assert_equal values.sort, values |
|
851 | assert_equal values.sort, values | |
836 | end |
|
852 | end | |
837 |
|
853 | |||
838 | def test_sort_by_string_custom_field_desc |
|
854 | def test_sort_by_string_custom_field_desc | |
839 | q = Query.new |
|
855 | q = Query.new | |
840 | c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } |
|
856 | c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } | |
841 | assert c |
|
857 | assert c | |
842 | assert c.sortable |
|
858 | assert c.sortable | |
843 | issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( |
|
859 | issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( | |
844 | q.statement |
|
860 | q.statement | |
845 | ).order("#{c.sortable} DESC").all |
|
861 | ).order("#{c.sortable} DESC").all | |
846 | values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s} |
|
862 | values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s} | |
847 | assert !values.empty? |
|
863 | assert !values.empty? | |
848 | assert_equal values.sort.reverse, values |
|
864 | assert_equal values.sort.reverse, values | |
849 | end |
|
865 | end | |
850 |
|
866 | |||
851 | def test_sort_by_float_custom_field_asc |
|
867 | def test_sort_by_float_custom_field_asc | |
852 | q = Query.new |
|
868 | q = Query.new | |
853 | c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' } |
|
869 | c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' } | |
854 | assert c |
|
870 | assert c | |
855 | assert c.sortable |
|
871 | assert c.sortable | |
856 | issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( |
|
872 | issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( | |
857 | q.statement |
|
873 | q.statement | |
858 | ).order("#{c.sortable} ASC").all |
|
874 | ).order("#{c.sortable} ASC").all | |
859 | values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact |
|
875 | values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact | |
860 | assert !values.empty? |
|
876 | assert !values.empty? | |
861 | assert_equal values.sort, values |
|
877 | assert_equal values.sort, values | |
862 | end |
|
878 | end | |
863 |
|
879 | |||
864 | def test_invalid_query_should_raise_query_statement_invalid_error |
|
880 | def test_invalid_query_should_raise_query_statement_invalid_error | |
865 | q = Query.new |
|
881 | q = Query.new | |
866 | assert_raise Query::StatementInvalid do |
|
882 | assert_raise Query::StatementInvalid do | |
867 | q.issues(:conditions => "foo = 1") |
|
883 | q.issues(:conditions => "foo = 1") | |
868 | end |
|
884 | end | |
869 | end |
|
885 | end | |
870 |
|
886 | |||
871 | def test_issue_count |
|
887 | def test_issue_count | |
872 | q = Query.new(:name => '_') |
|
888 | q = Query.new(:name => '_') | |
873 | issue_count = q.issue_count |
|
889 | issue_count = q.issue_count | |
874 | assert_equal q.issues.size, issue_count |
|
890 | assert_equal q.issues.size, issue_count | |
875 | end |
|
891 | end | |
876 |
|
892 | |||
877 | def test_issue_count_with_archived_issues |
|
893 | def test_issue_count_with_archived_issues | |
878 | p = Project.generate! do |project| |
|
894 | p = Project.generate! do |project| | |
879 | project.status = Project::STATUS_ARCHIVED |
|
895 | project.status = Project::STATUS_ARCHIVED | |
880 | end |
|
896 | end | |
881 | i = Issue.generate!( :project => p, :tracker => p.trackers.first ) |
|
897 | i = Issue.generate!( :project => p, :tracker => p.trackers.first ) | |
882 | assert !i.visible? |
|
898 | assert !i.visible? | |
883 |
|
899 | |||
884 | test_issue_count |
|
900 | test_issue_count | |
885 | end |
|
901 | end | |
886 |
|
902 | |||
887 | def test_issue_count_by_association_group |
|
903 | def test_issue_count_by_association_group | |
888 | q = Query.new(:name => '_', :group_by => 'assigned_to') |
|
904 | q = Query.new(:name => '_', :group_by => 'assigned_to') | |
889 | count_by_group = q.issue_count_by_group |
|
905 | count_by_group = q.issue_count_by_group | |
890 | assert_kind_of Hash, count_by_group |
|
906 | assert_kind_of Hash, count_by_group | |
891 | assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort |
|
907 | assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort | |
892 | assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq |
|
908 | assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq | |
893 | assert count_by_group.has_key?(User.find(3)) |
|
909 | assert count_by_group.has_key?(User.find(3)) | |
894 | end |
|
910 | end | |
895 |
|
911 | |||
896 | def test_issue_count_by_list_custom_field_group |
|
912 | def test_issue_count_by_list_custom_field_group | |
897 | q = Query.new(:name => '_', :group_by => 'cf_1') |
|
913 | q = Query.new(:name => '_', :group_by => 'cf_1') | |
898 | count_by_group = q.issue_count_by_group |
|
914 | count_by_group = q.issue_count_by_group | |
899 | assert_kind_of Hash, count_by_group |
|
915 | assert_kind_of Hash, count_by_group | |
900 | assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort |
|
916 | assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort | |
901 | assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq |
|
917 | assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq | |
902 | assert count_by_group.has_key?('MySQL') |
|
918 | assert count_by_group.has_key?('MySQL') | |
903 | end |
|
919 | end | |
904 |
|
920 | |||
905 | def test_issue_count_by_date_custom_field_group |
|
921 | def test_issue_count_by_date_custom_field_group | |
906 | q = Query.new(:name => '_', :group_by => 'cf_8') |
|
922 | q = Query.new(:name => '_', :group_by => 'cf_8') | |
907 | count_by_group = q.issue_count_by_group |
|
923 | count_by_group = q.issue_count_by_group | |
908 | assert_kind_of Hash, count_by_group |
|
924 | assert_kind_of Hash, count_by_group | |
909 | assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort |
|
925 | assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort | |
910 | assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq |
|
926 | assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq | |
911 | end |
|
927 | end | |
912 |
|
928 | |||
913 | def test_issue_count_with_nil_group_only |
|
929 | def test_issue_count_with_nil_group_only | |
914 | Issue.update_all("assigned_to_id = NULL") |
|
930 | Issue.update_all("assigned_to_id = NULL") | |
915 |
|
931 | |||
916 | q = Query.new(:name => '_', :group_by => 'assigned_to') |
|
932 | q = Query.new(:name => '_', :group_by => 'assigned_to') | |
917 | count_by_group = q.issue_count_by_group |
|
933 | count_by_group = q.issue_count_by_group | |
918 | assert_kind_of Hash, count_by_group |
|
934 | assert_kind_of Hash, count_by_group | |
919 | assert_equal 1, count_by_group.keys.size |
|
935 | assert_equal 1, count_by_group.keys.size | |
920 | assert_nil count_by_group.keys.first |
|
936 | assert_nil count_by_group.keys.first | |
921 | end |
|
937 | end | |
922 |
|
938 | |||
923 | def test_issue_ids |
|
939 | def test_issue_ids | |
924 | q = Query.new(:name => '_') |
|
940 | q = Query.new(:name => '_') | |
925 | order = "issues.subject, issues.id" |
|
941 | order = "issues.subject, issues.id" | |
926 | issues = q.issues(:order => order) |
|
942 | issues = q.issues(:order => order) | |
927 | assert_equal issues.map(&:id), q.issue_ids(:order => order) |
|
943 | assert_equal issues.map(&:id), q.issue_ids(:order => order) | |
928 | end |
|
944 | end | |
929 |
|
945 | |||
930 | def test_label_for |
|
946 | def test_label_for | |
931 | set_language_if_valid 'en' |
|
947 | set_language_if_valid 'en' | |
932 | q = Query.new |
|
948 | q = Query.new | |
933 | assert_equal 'Assignee', q.label_for('assigned_to_id') |
|
949 | assert_equal 'Assignee', q.label_for('assigned_to_id') | |
934 | end |
|
950 | end | |
935 |
|
951 | |||
936 | def test_label_for_fr |
|
952 | def test_label_for_fr | |
937 | set_language_if_valid 'fr' |
|
953 | set_language_if_valid 'fr' | |
938 | q = Query.new |
|
954 | q = Query.new | |
939 | s = "Assign\xc3\xa9 \xc3\xa0" |
|
955 | s = "Assign\xc3\xa9 \xc3\xa0" | |
940 | s.force_encoding('UTF-8') if s.respond_to?(:force_encoding) |
|
956 | s.force_encoding('UTF-8') if s.respond_to?(:force_encoding) | |
941 | assert_equal s, q.label_for('assigned_to_id') |
|
957 | assert_equal s, q.label_for('assigned_to_id') | |
942 | end |
|
958 | end | |
943 |
|
959 | |||
944 | def test_editable_by |
|
960 | def test_editable_by | |
945 | admin = User.find(1) |
|
961 | admin = User.find(1) | |
946 | manager = User.find(2) |
|
962 | manager = User.find(2) | |
947 | developer = User.find(3) |
|
963 | developer = User.find(3) | |
948 |
|
964 | |||
949 | # Public query on project 1 |
|
965 | # Public query on project 1 | |
950 | q = Query.find(1) |
|
966 | q = Query.find(1) | |
951 | assert q.editable_by?(admin) |
|
967 | assert q.editable_by?(admin) | |
952 | assert q.editable_by?(manager) |
|
968 | assert q.editable_by?(manager) | |
953 | assert !q.editable_by?(developer) |
|
969 | assert !q.editable_by?(developer) | |
954 |
|
970 | |||
955 | # Private query on project 1 |
|
971 | # Private query on project 1 | |
956 | q = Query.find(2) |
|
972 | q = Query.find(2) | |
957 | assert q.editable_by?(admin) |
|
973 | assert q.editable_by?(admin) | |
958 | assert !q.editable_by?(manager) |
|
974 | assert !q.editable_by?(manager) | |
959 | assert q.editable_by?(developer) |
|
975 | assert q.editable_by?(developer) | |
960 |
|
976 | |||
961 | # Private query for all projects |
|
977 | # Private query for all projects | |
962 | q = Query.find(3) |
|
978 | q = Query.find(3) | |
963 | assert q.editable_by?(admin) |
|
979 | assert q.editable_by?(admin) | |
964 | assert !q.editable_by?(manager) |
|
980 | assert !q.editable_by?(manager) | |
965 | assert q.editable_by?(developer) |
|
981 | assert q.editable_by?(developer) | |
966 |
|
982 | |||
967 | # Public query for all projects |
|
983 | # Public query for all projects | |
968 | q = Query.find(4) |
|
984 | q = Query.find(4) | |
969 | assert q.editable_by?(admin) |
|
985 | assert q.editable_by?(admin) | |
970 | assert !q.editable_by?(manager) |
|
986 | assert !q.editable_by?(manager) | |
971 | assert !q.editable_by?(developer) |
|
987 | assert !q.editable_by?(developer) | |
972 | end |
|
988 | end | |
973 |
|
989 | |||
974 | def test_visible_scope |
|
990 | def test_visible_scope | |
975 | query_ids = Query.visible(User.anonymous).map(&:id) |
|
991 | query_ids = Query.visible(User.anonymous).map(&:id) | |
976 |
|
992 | |||
977 | assert query_ids.include?(1), 'public query on public project was not visible' |
|
993 | assert query_ids.include?(1), 'public query on public project was not visible' | |
978 | assert query_ids.include?(4), 'public query for all projects was not visible' |
|
994 | assert query_ids.include?(4), 'public query for all projects was not visible' | |
979 | assert !query_ids.include?(2), 'private query on public project was visible' |
|
995 | assert !query_ids.include?(2), 'private query on public project was visible' | |
980 | assert !query_ids.include?(3), 'private query for all projects was visible' |
|
996 | assert !query_ids.include?(3), 'private query for all projects was visible' | |
981 | assert !query_ids.include?(7), 'public query on private project was visible' |
|
997 | assert !query_ids.include?(7), 'public query on private project was visible' | |
982 | end |
|
998 | end | |
983 |
|
999 | |||
984 | context "#available_filters" do |
|
1000 | context "#available_filters" do | |
985 | setup do |
|
1001 | setup do | |
986 | @query = Query.new(:name => "_") |
|
1002 | @query = Query.new(:name => "_") | |
987 | end |
|
1003 | end | |
988 |
|
1004 | |||
989 | should "include users of visible projects in cross-project view" do |
|
1005 | should "include users of visible projects in cross-project view" do | |
990 | users = @query.available_filters["assigned_to_id"] |
|
1006 | users = @query.available_filters["assigned_to_id"] | |
991 | assert_not_nil users |
|
1007 | assert_not_nil users | |
992 | assert users[:values].map{|u|u[1]}.include?("3") |
|
1008 | assert users[:values].map{|u|u[1]}.include?("3") | |
993 | end |
|
1009 | end | |
994 |
|
1010 | |||
995 | should "include users of subprojects" do |
|
1011 | should "include users of subprojects" do | |
996 | user1 = User.generate! |
|
1012 | user1 = User.generate! | |
997 | user2 = User.generate! |
|
1013 | user2 = User.generate! | |
998 | project = Project.find(1) |
|
1014 | project = Project.find(1) | |
999 | Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1]) |
|
1015 | Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1]) | |
1000 | @query.project = project |
|
1016 | @query.project = project | |
1001 |
|
1017 | |||
1002 | users = @query.available_filters["assigned_to_id"] |
|
1018 | users = @query.available_filters["assigned_to_id"] | |
1003 | assert_not_nil users |
|
1019 | assert_not_nil users | |
1004 | assert users[:values].map{|u|u[1]}.include?(user1.id.to_s) |
|
1020 | assert users[:values].map{|u|u[1]}.include?(user1.id.to_s) | |
1005 | assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s) |
|
1021 | assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s) | |
1006 | end |
|
1022 | end | |
1007 |
|
1023 | |||
1008 | should "include visible projects in cross-project view" do |
|
1024 | should "include visible projects in cross-project view" do | |
1009 | projects = @query.available_filters["project_id"] |
|
1025 | projects = @query.available_filters["project_id"] | |
1010 | assert_not_nil projects |
|
1026 | assert_not_nil projects | |
1011 | assert projects[:values].map{|u|u[1]}.include?("1") |
|
1027 | assert projects[:values].map{|u|u[1]}.include?("1") | |
1012 | end |
|
1028 | end | |
1013 |
|
1029 | |||
1014 | context "'member_of_group' filter" do |
|
1030 | context "'member_of_group' filter" do | |
1015 | should "be present" do |
|
1031 | should "be present" do | |
1016 | assert @query.available_filters.keys.include?("member_of_group") |
|
1032 | assert @query.available_filters.keys.include?("member_of_group") | |
1017 | end |
|
1033 | end | |
1018 |
|
1034 | |||
1019 | should "be an optional list" do |
|
1035 | should "be an optional list" do | |
1020 | assert_equal :list_optional, @query.available_filters["member_of_group"][:type] |
|
1036 | assert_equal :list_optional, @query.available_filters["member_of_group"][:type] | |
1021 | end |
|
1037 | end | |
1022 |
|
1038 | |||
1023 | should "have a list of the groups as values" do |
|
1039 | should "have a list of the groups as values" do | |
1024 | Group.destroy_all # No fixtures |
|
1040 | Group.destroy_all # No fixtures | |
1025 | group1 = Group.generate!.reload |
|
1041 | group1 = Group.generate!.reload | |
1026 | group2 = Group.generate!.reload |
|
1042 | group2 = Group.generate!.reload | |
1027 |
|
1043 | |||
1028 | expected_group_list = [ |
|
1044 | expected_group_list = [ | |
1029 | [group1.name, group1.id.to_s], |
|
1045 | [group1.name, group1.id.to_s], | |
1030 | [group2.name, group2.id.to_s] |
|
1046 | [group2.name, group2.id.to_s] | |
1031 | ] |
|
1047 | ] | |
1032 | assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort |
|
1048 | assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort | |
1033 | end |
|
1049 | end | |
1034 |
|
1050 | |||
1035 | end |
|
1051 | end | |
1036 |
|
1052 | |||
1037 | context "'assigned_to_role' filter" do |
|
1053 | context "'assigned_to_role' filter" do | |
1038 | should "be present" do |
|
1054 | should "be present" do | |
1039 | assert @query.available_filters.keys.include?("assigned_to_role") |
|
1055 | assert @query.available_filters.keys.include?("assigned_to_role") | |
1040 | end |
|
1056 | end | |
1041 |
|
1057 | |||
1042 | should "be an optional list" do |
|
1058 | should "be an optional list" do | |
1043 | assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type] |
|
1059 | assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type] | |
1044 | end |
|
1060 | end | |
1045 |
|
1061 | |||
1046 | should "have a list of the Roles as values" do |
|
1062 | should "have a list of the Roles as values" do | |
1047 | assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1']) |
|
1063 | assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1']) | |
1048 | assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2']) |
|
1064 | assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2']) | |
1049 | assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3']) |
|
1065 | assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3']) | |
1050 | end |
|
1066 | end | |
1051 |
|
1067 | |||
1052 | should "not include the built in Roles as values" do |
|
1068 | should "not include the built in Roles as values" do | |
1053 | assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4']) |
|
1069 | assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4']) | |
1054 | assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5']) |
|
1070 | assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5']) | |
1055 | end |
|
1071 | end | |
1056 |
|
1072 | |||
1057 | end |
|
1073 | end | |
1058 |
|
1074 | |||
1059 | end |
|
1075 | end | |
1060 |
|
1076 | |||
1061 | context "#statement" do |
|
1077 | context "#statement" do | |
1062 | context "with 'member_of_group' filter" do |
|
1078 | context "with 'member_of_group' filter" do | |
1063 | setup do |
|
1079 | setup do | |
1064 | Group.destroy_all # No fixtures |
|
1080 | Group.destroy_all # No fixtures | |
1065 | @user_in_group = User.generate! |
|
1081 | @user_in_group = User.generate! | |
1066 | @second_user_in_group = User.generate! |
|
1082 | @second_user_in_group = User.generate! | |
1067 | @user_in_group2 = User.generate! |
|
1083 | @user_in_group2 = User.generate! | |
1068 | @user_not_in_group = User.generate! |
|
1084 | @user_not_in_group = User.generate! | |
1069 |
|
1085 | |||
1070 | @group = Group.generate!.reload |
|
1086 | @group = Group.generate!.reload | |
1071 | @group.users << @user_in_group |
|
1087 | @group.users << @user_in_group | |
1072 | @group.users << @second_user_in_group |
|
1088 | @group.users << @second_user_in_group | |
1073 |
|
1089 | |||
1074 | @group2 = Group.generate!.reload |
|
1090 | @group2 = Group.generate!.reload | |
1075 | @group2.users << @user_in_group2 |
|
1091 | @group2.users << @user_in_group2 | |
1076 |
|
1092 | |||
1077 | end |
|
1093 | end | |
1078 |
|
1094 | |||
1079 | should "search assigned to for users in the group" do |
|
1095 | should "search assigned to for users in the group" do | |
1080 | @query = Query.new(:name => '_') |
|
1096 | @query = Query.new(:name => '_') | |
1081 | @query.add_filter('member_of_group', '=', [@group.id.to_s]) |
|
1097 | @query.add_filter('member_of_group', '=', [@group.id.to_s]) | |
1082 |
|
1098 | |||
1083 | assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')" |
|
1099 | assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')" | |
1084 | assert_find_issues_with_query_is_successful @query |
|
1100 | assert_find_issues_with_query_is_successful @query | |
1085 | end |
|
1101 | end | |
1086 |
|
1102 | |||
1087 | should "search not assigned to any group member (none)" do |
|
1103 | should "search not assigned to any group member (none)" do | |
1088 | @query = Query.new(:name => '_') |
|
1104 | @query = Query.new(:name => '_') | |
1089 | @query.add_filter('member_of_group', '!*', ['']) |
|
1105 | @query.add_filter('member_of_group', '!*', ['']) | |
1090 |
|
1106 | |||
1091 | # Users not in a group |
|
1107 | # Users not in a group | |
1092 | 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}')" |
|
1108 | 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}')" | |
1093 | assert_find_issues_with_query_is_successful @query |
|
1109 | assert_find_issues_with_query_is_successful @query | |
1094 | end |
|
1110 | end | |
1095 |
|
1111 | |||
1096 | should "search assigned to any group member (all)" do |
|
1112 | should "search assigned to any group member (all)" do | |
1097 | @query = Query.new(:name => '_') |
|
1113 | @query = Query.new(:name => '_') | |
1098 | @query.add_filter('member_of_group', '*', ['']) |
|
1114 | @query.add_filter('member_of_group', '*', ['']) | |
1099 |
|
1115 | |||
1100 | # Only users in a group |
|
1116 | # Only users in a group | |
1101 | 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}')" |
|
1117 | 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}')" | |
1102 | assert_find_issues_with_query_is_successful @query |
|
1118 | assert_find_issues_with_query_is_successful @query | |
1103 | end |
|
1119 | end | |
1104 |
|
1120 | |||
1105 | should "return an empty set with = empty group" do |
|
1121 | should "return an empty set with = empty group" do | |
1106 | @empty_group = Group.generate! |
|
1122 | @empty_group = Group.generate! | |
1107 | @query = Query.new(:name => '_') |
|
1123 | @query = Query.new(:name => '_') | |
1108 | @query.add_filter('member_of_group', '=', [@empty_group.id.to_s]) |
|
1124 | @query.add_filter('member_of_group', '=', [@empty_group.id.to_s]) | |
1109 |
|
1125 | |||
1110 | assert_equal [], find_issues_with_query(@query) |
|
1126 | assert_equal [], find_issues_with_query(@query) | |
1111 | end |
|
1127 | end | |
1112 |
|
1128 | |||
1113 | should "return issues with ! empty group" do |
|
1129 | should "return issues with ! empty group" do | |
1114 | @empty_group = Group.generate! |
|
1130 | @empty_group = Group.generate! | |
1115 | @query = Query.new(:name => '_') |
|
1131 | @query = Query.new(:name => '_') | |
1116 | @query.add_filter('member_of_group', '!', [@empty_group.id.to_s]) |
|
1132 | @query.add_filter('member_of_group', '!', [@empty_group.id.to_s]) | |
1117 |
|
1133 | |||
1118 | assert_find_issues_with_query_is_successful @query |
|
1134 | assert_find_issues_with_query_is_successful @query | |
1119 | end |
|
1135 | end | |
1120 | end |
|
1136 | end | |
1121 |
|
1137 | |||
1122 | context "with 'assigned_to_role' filter" do |
|
1138 | context "with 'assigned_to_role' filter" do | |
1123 | setup do |
|
1139 | setup do | |
1124 | @manager_role = Role.find_by_name('Manager') |
|
1140 | @manager_role = Role.find_by_name('Manager') | |
1125 | @developer_role = Role.find_by_name('Developer') |
|
1141 | @developer_role = Role.find_by_name('Developer') | |
1126 |
|
1142 | |||
1127 | @project = Project.generate! |
|
1143 | @project = Project.generate! | |
1128 | @manager = User.generate! |
|
1144 | @manager = User.generate! | |
1129 | @developer = User.generate! |
|
1145 | @developer = User.generate! | |
1130 | @boss = User.generate! |
|
1146 | @boss = User.generate! | |
1131 | @guest = User.generate! |
|
1147 | @guest = User.generate! | |
1132 | User.add_to_project(@manager, @project, @manager_role) |
|
1148 | User.add_to_project(@manager, @project, @manager_role) | |
1133 | User.add_to_project(@developer, @project, @developer_role) |
|
1149 | User.add_to_project(@developer, @project, @developer_role) | |
1134 | User.add_to_project(@boss, @project, [@manager_role, @developer_role]) |
|
1150 | User.add_to_project(@boss, @project, [@manager_role, @developer_role]) | |
1135 |
|
1151 | |||
1136 | @issue1 = Issue.generate_for_project!(@project, :assigned_to_id => @manager.id) |
|
1152 | @issue1 = Issue.generate_for_project!(@project, :assigned_to_id => @manager.id) | |
1137 | @issue2 = Issue.generate_for_project!(@project, :assigned_to_id => @developer.id) |
|
1153 | @issue2 = Issue.generate_for_project!(@project, :assigned_to_id => @developer.id) | |
1138 | @issue3 = Issue.generate_for_project!(@project, :assigned_to_id => @boss.id) |
|
1154 | @issue3 = Issue.generate_for_project!(@project, :assigned_to_id => @boss.id) | |
1139 | @issue4 = Issue.generate_for_project!(@project, :assigned_to_id => @guest.id) |
|
1155 | @issue4 = Issue.generate_for_project!(@project, :assigned_to_id => @guest.id) | |
1140 | @issue5 = Issue.generate_for_project!(@project) |
|
1156 | @issue5 = Issue.generate_for_project!(@project) | |
1141 | end |
|
1157 | end | |
1142 |
|
1158 | |||
1143 | should "search assigned to for users with the Role" do |
|
1159 | should "search assigned to for users with the Role" do | |
1144 | @query = Query.new(:name => '_', :project => @project) |
|
1160 | @query = Query.new(:name => '_', :project => @project) | |
1145 | @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) |
|
1161 | @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) | |
1146 |
|
1162 | |||
1147 | assert_query_result [@issue1, @issue3], @query |
|
1163 | assert_query_result [@issue1, @issue3], @query | |
1148 | end |
|
1164 | end | |
1149 |
|
1165 | |||
1150 | should "search assigned to for users with the Role on the issue project" do |
|
1166 | should "search assigned to for users with the Role on the issue project" do | |
1151 | other_project = Project.generate! |
|
1167 | other_project = Project.generate! | |
1152 | User.add_to_project(@developer, other_project, @manager_role) |
|
1168 | User.add_to_project(@developer, other_project, @manager_role) | |
1153 |
|
1169 | |||
1154 | @query = Query.new(:name => '_', :project => @project) |
|
1170 | @query = Query.new(:name => '_', :project => @project) | |
1155 | @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) |
|
1171 | @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) | |
1156 |
|
1172 | |||
1157 | assert_query_result [@issue1, @issue3], @query |
|
1173 | assert_query_result [@issue1, @issue3], @query | |
1158 | end |
|
1174 | end | |
1159 |
|
1175 | |||
1160 | should "return an empty set with empty role" do |
|
1176 | should "return an empty set with empty role" do | |
1161 | @empty_role = Role.generate! |
|
1177 | @empty_role = Role.generate! | |
1162 | @query = Query.new(:name => '_', :project => @project) |
|
1178 | @query = Query.new(:name => '_', :project => @project) | |
1163 | @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s]) |
|
1179 | @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s]) | |
1164 |
|
1180 | |||
1165 | assert_query_result [], @query |
|
1181 | assert_query_result [], @query | |
1166 | end |
|
1182 | end | |
1167 |
|
1183 | |||
1168 | should "search assigned to for users without the Role" do |
|
1184 | should "search assigned to for users without the Role" do | |
1169 | @query = Query.new(:name => '_', :project => @project) |
|
1185 | @query = Query.new(:name => '_', :project => @project) | |
1170 | @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s]) |
|
1186 | @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s]) | |
1171 |
|
1187 | |||
1172 | assert_query_result [@issue2, @issue4, @issue5], @query |
|
1188 | assert_query_result [@issue2, @issue4, @issue5], @query | |
1173 | end |
|
1189 | end | |
1174 |
|
1190 | |||
1175 | should "search assigned to for users not assigned to any Role (none)" do |
|
1191 | should "search assigned to for users not assigned to any Role (none)" do | |
1176 | @query = Query.new(:name => '_', :project => @project) |
|
1192 | @query = Query.new(:name => '_', :project => @project) | |
1177 | @query.add_filter('assigned_to_role', '!*', ['']) |
|
1193 | @query.add_filter('assigned_to_role', '!*', ['']) | |
1178 |
|
1194 | |||
1179 | assert_query_result [@issue4, @issue5], @query |
|
1195 | assert_query_result [@issue4, @issue5], @query | |
1180 | end |
|
1196 | end | |
1181 |
|
1197 | |||
1182 | should "search assigned to for users assigned to any Role (all)" do |
|
1198 | should "search assigned to for users assigned to any Role (all)" do | |
1183 | @query = Query.new(:name => '_', :project => @project) |
|
1199 | @query = Query.new(:name => '_', :project => @project) | |
1184 | @query.add_filter('assigned_to_role', '*', ['']) |
|
1200 | @query.add_filter('assigned_to_role', '*', ['']) | |
1185 |
|
1201 | |||
1186 | assert_query_result [@issue1, @issue2, @issue3], @query |
|
1202 | assert_query_result [@issue1, @issue2, @issue3], @query | |
1187 | end |
|
1203 | end | |
1188 |
|
1204 | |||
1189 | should "return issues with ! empty role" do |
|
1205 | should "return issues with ! empty role" do | |
1190 | @empty_role = Role.generate! |
|
1206 | @empty_role = Role.generate! | |
1191 | @query = Query.new(:name => '_', :project => @project) |
|
1207 | @query = Query.new(:name => '_', :project => @project) | |
1192 | @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s]) |
|
1208 | @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s]) | |
1193 |
|
1209 | |||
1194 | assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query |
|
1210 | assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query | |
1195 | end |
|
1211 | end | |
1196 | end |
|
1212 | end | |
1197 | end |
|
1213 | end | |
1198 |
|
1214 | |||
1199 | end |
|
1215 | end |
General Comments 0
You need to be logged in to leave comments.
Login now