##// END OF EJS Templates
Adds a Setting to control how an Issue's done_ratio is calculated:...
Eric Davis -
r3037:4fe14e71c2d7
parent child
Show More
@@ -0,0 +1,9
1 class AddDefaultDoneRatioToIssueStatus < ActiveRecord::Migration
2 def self.up
3 add_column :issue_statuses, :default_done_ratio, :integer
4 end
5
6 def self.down
7 remove_column :issue_statuses, :default_done_ratio
8 end
9 end
1 NO CONTENT: new file 100644, binary diff hidden
@@ -1,69 +1,78
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssueStatusesController < ApplicationController
19 19 before_filter :require_admin
20 20
21 verify :method => :post, :only => [ :destroy, :create, :update, :move ],
21 verify :method => :post, :only => [ :destroy, :create, :update, :move, :update_issue_done_ratio ],
22 22 :redirect_to => { :action => :list }
23 23
24 24 def index
25 25 list
26 26 render :action => 'list' unless request.xhr?
27 27 end
28 28
29 29 def list
30 30 @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
31 31 render :action => "list", :layout => false if request.xhr?
32 32 end
33 33
34 34 def new
35 35 @issue_status = IssueStatus.new
36 36 end
37 37
38 38 def create
39 39 @issue_status = IssueStatus.new(params[:issue_status])
40 40 if @issue_status.save
41 41 flash[:notice] = l(:notice_successful_create)
42 42 redirect_to :action => 'list'
43 43 else
44 44 render :action => 'new'
45 45 end
46 46 end
47 47
48 48 def edit
49 49 @issue_status = IssueStatus.find(params[:id])
50 50 end
51 51
52 52 def update
53 53 @issue_status = IssueStatus.find(params[:id])
54 54 if @issue_status.update_attributes(params[:issue_status])
55 55 flash[:notice] = l(:notice_successful_update)
56 56 redirect_to :action => 'list'
57 57 else
58 58 render :action => 'edit'
59 59 end
60 60 end
61 61
62 62 def destroy
63 63 IssueStatus.find(params[:id]).destroy
64 64 redirect_to :action => 'list'
65 65 rescue
66 66 flash[:error] = "Unable to delete issue status"
67 67 redirect_to :action => 'list'
68 68 end
69
70 def update_issue_done_ratio
71 if IssueStatus.update_issue_done_ratios
72 flash[:notice] = l(:notice_issue_done_ratios_updated)
73 else
74 flash[:error] = l(:error_issue_done_ratios_not_updated)
75 end
76 redirect_to :action => 'list'
77 end
69 78 end
@@ -1,425 +1,452
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Issue < ActiveRecord::Base
19 19 belongs_to :project
20 20 belongs_to :tracker
21 21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
22 22 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
23 23 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
24 24 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
25 25 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
26 26 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
27 27
28 28 has_many :journals, :as => :journalized, :dependent => :destroy
29 29 has_many :time_entries, :dependent => :delete_all
30 30 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
31 31
32 32 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
33 33 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
34 34
35 35 acts_as_attachable :after_remove => :attachment_removed
36 36 acts_as_customizable
37 37 acts_as_watchable
38 38 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
39 39 :include => [:project, :journals],
40 40 # sort by id so that limited eager loading doesn't break with postgresql
41 41 :order_column => "#{table_name}.id"
42 42 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
43 43 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
44 44 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
45 45
46 46 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
47 47 :author_key => :author_id
48
49 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
48 50
49 51 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
50 52 validates_length_of :subject, :maximum => 255
51 53 validates_inclusion_of :done_ratio, :in => 0..100
52 54 validates_numericality_of :estimated_hours, :allow_nil => true
53 55
54 56 named_scope :visible, lambda {|*args| { :include => :project,
55 57 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
56 58
57 59 named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
58
60
61 before_save :update_done_ratio_from_issue_status
59 62 after_save :create_journal
60 63
61 64 # Returns true if usr or current user is allowed to view the issue
62 65 def visible?(usr=nil)
63 66 (usr || User.current).allowed_to?(:view_issues, self.project)
64 67 end
65 68
66 69 def after_initialize
67 70 if new_record?
68 71 # set default values for new records only
69 72 self.status ||= IssueStatus.default
70 73 self.priority ||= IssuePriority.default
71 74 end
72 75 end
73 76
74 77 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
75 78 def available_custom_fields
76 79 (project && tracker) ? project.all_issue_custom_fields.select {|c| tracker.custom_fields.include? c } : []
77 80 end
78 81
79 82 def copy_from(arg)
80 83 issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
81 84 self.attributes = issue.attributes.dup.except("id", "created_on", "updated_on")
82 85 self.custom_values = issue.custom_values.collect {|v| v.clone}
83 86 self.status = issue.status
84 87 self
85 88 end
86 89
87 90 # Moves/copies an issue to a new project and tracker
88 91 # Returns the moved/copied issue on success, false on failure
89 92 def move_to(new_project, new_tracker = nil, options = {})
90 93 options ||= {}
91 94 issue = options[:copy] ? self.clone : self
92 95 transaction do
93 96 if new_project && issue.project_id != new_project.id
94 97 # delete issue relations
95 98 unless Setting.cross_project_issue_relations?
96 99 issue.relations_from.clear
97 100 issue.relations_to.clear
98 101 end
99 102 # issue is moved to another project
100 103 # reassign to the category with same name if any
101 104 new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
102 105 issue.category = new_category
103 106 # Keep the fixed_version if it's still valid in the new_project
104 107 unless new_project.shared_versions.include?(issue.fixed_version)
105 108 issue.fixed_version = nil
106 109 end
107 110 issue.project = new_project
108 111 end
109 112 if new_tracker
110 113 issue.tracker = new_tracker
111 114 end
112 115 if options[:copy]
113 116 issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
114 117 issue.status = if options[:attributes] && options[:attributes][:status_id]
115 118 IssueStatus.find_by_id(options[:attributes][:status_id])
116 119 else
117 120 self.status
118 121 end
119 122 end
120 123 # Allow bulk setting of attributes on the issue
121 124 if options[:attributes]
122 125 issue.attributes = options[:attributes]
123 126 end
124 127 if issue.save
125 128 unless options[:copy]
126 129 # Manually update project_id on related time entries
127 130 TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
128 131 end
129 132 else
130 133 Issue.connection.rollback_db_transaction
131 134 return false
132 135 end
133 136 end
134 137 return issue
135 138 end
136 139
137 140 def priority_id=(pid)
138 141 self.priority = nil
139 142 write_attribute(:priority_id, pid)
140 143 end
141 144
142 145 def tracker_id=(tid)
143 146 self.tracker = nil
144 147 write_attribute(:tracker_id, tid)
145 148 result = write_attribute(:tracker_id, tid)
146 149 @custom_field_values = nil
147 150 result
148 151 end
149 152
150 153 # Overrides attributes= so that tracker_id gets assigned first
151 154 def attributes_with_tracker_first=(new_attributes, *args)
152 155 return if new_attributes.nil?
153 156 new_tracker_id = new_attributes['tracker_id'] || new_attributes[:tracker_id]
154 157 if new_tracker_id
155 158 self.tracker_id = new_tracker_id
156 159 end
157 160 self.attributes_without_tracker_first = new_attributes, *args
158 161 end
159 162 alias_method_chain :attributes=, :tracker_first
160 163
161 164 def estimated_hours=(h)
162 165 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
163 166 end
164 167
168 def done_ratio
169 if Issue.use_status_for_done_ratio? && !self.status.default_done_ratio.blank?
170 self.status.default_done_ratio
171 else
172 read_attribute(:done_ratio)
173 end
174 end
175
176 def self.use_status_for_done_ratio?
177 Setting.issue_done_ratio == 'issue_status'
178 end
179
180 def self.use_field_for_done_ratio?
181 Setting.issue_done_ratio == 'issue_field'
182 end
183
165 184 def validate
166 185 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
167 186 errors.add :due_date, :not_a_date
168 187 end
169 188
170 189 if self.due_date and self.start_date and self.due_date < self.start_date
171 190 errors.add :due_date, :greater_than_start_date
172 191 end
173 192
174 193 if start_date && soonest_start && start_date < soonest_start
175 194 errors.add :start_date, :invalid
176 195 end
177 196
178 197 if fixed_version
179 198 if !assignable_versions.include?(fixed_version)
180 199 errors.add :fixed_version_id, :inclusion
181 200 elsif reopened? && fixed_version.closed?
182 201 errors.add_to_base I18n.t(:error_can_not_reopen_issue_on_closed_version)
183 202 end
184 203 end
185 204
186 205 # Checks that the issue can not be added/moved to a disabled tracker
187 206 if project && (tracker_id_changed? || project_id_changed?)
188 207 unless project.trackers.include?(tracker)
189 208 errors.add :tracker_id, :inclusion
190 209 end
191 210 end
192 211 end
193 212
194 213 def before_create
195 214 # default assignment based on category
196 215 if assigned_to.nil? && category && category.assigned_to
197 216 self.assigned_to = category.assigned_to
198 217 end
199 218 end
200 219
220 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
221 # even if the user turns off the setting later
222 def update_done_ratio_from_issue_status
223 if Issue.use_status_for_done_ratio? && !self.status.default_done_ratio.blank?
224 self.done_ratio = self.status.default_done_ratio
225 end
226 end
227
201 228 def after_save
202 229 # Reload is needed in order to get the right status
203 230 reload
204 231
205 232 # Update start/due dates of following issues
206 233 relations_from.each(&:set_issue_to_dates)
207 234
208 235 # Close duplicates if the issue was closed
209 236 if @issue_before_change && !@issue_before_change.closed? && self.closed?
210 237 duplicates.each do |duplicate|
211 238 # Reload is need in case the duplicate was updated by a previous duplicate
212 239 duplicate.reload
213 240 # Don't re-close it if it's already closed
214 241 next if duplicate.closed?
215 242 # Same user and notes
216 243 duplicate.init_journal(@current_journal.user, @current_journal.notes)
217 244 duplicate.update_attribute :status, self.status
218 245 end
219 246 end
220 247 end
221 248
222 249 def init_journal(user, notes = "")
223 250 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
224 251 @issue_before_change = self.clone
225 252 @issue_before_change.status = self.status
226 253 @custom_values_before_change = {}
227 254 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
228 255 # Make sure updated_on is updated when adding a note.
229 256 updated_on_will_change!
230 257 @current_journal
231 258 end
232 259
233 260 # Return true if the issue is closed, otherwise false
234 261 def closed?
235 262 self.status.is_closed?
236 263 end
237 264
238 265 # Return true if the issue is being reopened
239 266 def reopened?
240 267 if !new_record? && status_id_changed?
241 268 status_was = IssueStatus.find_by_id(status_id_was)
242 269 status_new = IssueStatus.find_by_id(status_id)
243 270 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
244 271 return true
245 272 end
246 273 end
247 274 false
248 275 end
249 276
250 277 # Returns true if the issue is overdue
251 278 def overdue?
252 279 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
253 280 end
254 281
255 282 # Users the issue can be assigned to
256 283 def assignable_users
257 284 project.assignable_users
258 285 end
259 286
260 287 # Versions that the issue can be assigned to
261 288 def assignable_versions
262 289 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
263 290 end
264 291
265 292 # Returns true if this issue is blocked by another issue that is still open
266 293 def blocked?
267 294 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
268 295 end
269 296
270 297 # Returns an array of status that user is able to apply
271 298 def new_statuses_allowed_to(user)
272 299 statuses = status.find_new_statuses_allowed_to(user.roles_for_project(project), tracker)
273 300 statuses << status unless statuses.empty?
274 301 statuses = statuses.uniq.sort
275 302 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
276 303 end
277 304
278 305 # Returns the mail adresses of users that should be notified
279 306 def recipients
280 307 notified = project.notified_users
281 308 # Author and assignee are always notified unless they have been locked
282 309 notified << author if author && author.active?
283 310 notified << assigned_to if assigned_to && assigned_to.active?
284 311 notified.uniq!
285 312 # Remove users that can not view the issue
286 313 notified.reject! {|user| !visible?(user)}
287 314 notified.collect(&:mail)
288 315 end
289 316
290 317 # Returns the mail adresses of watchers that should be notified
291 318 def watcher_recipients
292 319 notified = watcher_users
293 320 notified.reject! {|user| !user.active? || !visible?(user)}
294 321 notified.collect(&:mail)
295 322 end
296 323
297 324 # Returns the total number of hours spent on this issue.
298 325 #
299 326 # Example:
300 327 # spent_hours => 0
301 328 # spent_hours => 50
302 329 def spent_hours
303 330 @spent_hours ||= time_entries.sum(:hours) || 0
304 331 end
305 332
306 333 def relations
307 334 (relations_from + relations_to).sort
308 335 end
309 336
310 337 def all_dependent_issues
311 338 dependencies = []
312 339 relations_from.each do |relation|
313 340 dependencies << relation.issue_to
314 341 dependencies += relation.issue_to.all_dependent_issues
315 342 end
316 343 dependencies
317 344 end
318 345
319 346 # Returns an array of issues that duplicate this one
320 347 def duplicates
321 348 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
322 349 end
323 350
324 351 # Returns the due date or the target due date if any
325 352 # Used on gantt chart
326 353 def due_before
327 354 due_date || (fixed_version ? fixed_version.effective_date : nil)
328 355 end
329 356
330 357 # Returns the time scheduled for this issue.
331 358 #
332 359 # Example:
333 360 # Start Date: 2/26/09, End Date: 3/04/09
334 361 # duration => 6
335 362 def duration
336 363 (start_date && due_date) ? due_date - start_date : 0
337 364 end
338 365
339 366 def soonest_start
340 367 @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
341 368 end
342 369
343 370 def to_s
344 371 "#{tracker} ##{id}: #{subject}"
345 372 end
346 373
347 374 # Returns a string of css classes that apply to the issue
348 375 def css_classes
349 376 s = "issue status-#{status.position} priority-#{priority.position}"
350 377 s << ' closed' if closed?
351 378 s << ' overdue' if overdue?
352 379 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
353 380 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
354 381 s
355 382 end
356 383
357 384 # Unassigns issues from +version+ if it's no longer shared with issue's project
358 385 def self.update_versions_from_sharing_change(version)
359 386 # Update issues assigned to the version
360 387 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
361 388 end
362 389
363 390 # Unassigns issues from versions that are no longer shared
364 391 # after +project+ was moved
365 392 def self.update_versions_from_hierarchy_change(project)
366 393 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
367 394 # Update issues of the moved projects and issues assigned to a version of a moved project
368 395 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
369 396 end
370 397
371 398 private
372 399
373 400 # Update issues so their versions are not pointing to a
374 401 # fixed_version that is not shared with the issue's project
375 402 def self.update_versions(conditions=nil)
376 403 # Only need to update issues with a fixed_version from
377 404 # a different project and that is not systemwide shared
378 405 Issue.all(:conditions => merge_conditions("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
379 406 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
380 407 " AND #{Version.table_name}.sharing <> 'system'",
381 408 conditions),
382 409 :include => [:project, :fixed_version]
383 410 ).each do |issue|
384 411 next if issue.project.nil? || issue.fixed_version.nil?
385 412 unless issue.project.shared_versions.include?(issue.fixed_version)
386 413 issue.init_journal(User.current)
387 414 issue.fixed_version = nil
388 415 issue.save
389 416 end
390 417 end
391 418 end
392 419
393 420 # Callback on attachment deletion
394 421 def attachment_removed(obj)
395 422 journal = init_journal(User.current)
396 423 journal.details << JournalDetail.new(:property => 'attachment',
397 424 :prop_key => obj.id,
398 425 :old_value => obj.filename)
399 426 journal.save
400 427 end
401 428
402 429 # Saves the changes in a Journal
403 430 # Called after_save
404 431 def create_journal
405 432 if @current_journal
406 433 # attributes changes
407 434 (Issue.column_names - %w(id description lock_version created_on updated_on)).each {|c|
408 435 @current_journal.details << JournalDetail.new(:property => 'attr',
409 436 :prop_key => c,
410 437 :old_value => @issue_before_change.send(c),
411 438 :value => send(c)) unless send(c)==@issue_before_change.send(c)
412 439 }
413 440 # custom fields changes
414 441 custom_values.each {|c|
415 442 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
416 443 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
417 444 @current_journal.details << JournalDetail.new(:property => 'cf',
418 445 :prop_key => c.custom_field_id,
419 446 :old_value => @custom_values_before_change[c.custom_field_id],
420 447 :value => c.value)
421 448 }
422 449 @current_journal.save
423 450 end
424 451 end
425 452 end
@@ -1,79 +1,91
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssueStatus < ActiveRecord::Base
19 19 before_destroy :check_integrity
20 20 has_many :workflows, :foreign_key => "old_status_id", :dependent => :delete_all
21 21 acts_as_list
22 22
23 23 validates_presence_of :name
24 24 validates_uniqueness_of :name
25 25 validates_length_of :name, :maximum => 30
26 26 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
27 27
28 28 def after_save
29 29 IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default?
30 30 end
31 31
32 32 # Returns the default status for new issues
33 33 def self.default
34 34 find(:first, :conditions =>["is_default=?", true])
35 35 end
36
37 # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+
38 def self.update_issue_done_ratios
39 if Issue.use_status_for_done_ratio?
40 IssueStatus.find(:all, :conditions => ["default_done_ratio >= 0"]).each do |status|
41 Issue.update_all(["done_ratio = ?", status.default_done_ratio],
42 ["status_id = ?", status.id])
43 end
44 end
45
46 return Issue.use_status_for_done_ratio?
47 end
36 48
37 49 # Returns an array of all statuses the given role can switch to
38 50 # Uses association cache when called more than one time
39 51 def new_statuses_allowed_to(roles, tracker)
40 52 if roles && tracker
41 53 role_ids = roles.collect(&:id)
42 54 new_statuses = workflows.select {|w| role_ids.include?(w.role_id) && w.tracker_id == tracker.id}.collect{|w| w.new_status}.compact.sort
43 55 else
44 56 []
45 57 end
46 58 end
47 59
48 60 # Same thing as above but uses a database query
49 61 # More efficient than the previous method if called just once
50 62 def find_new_statuses_allowed_to(roles, tracker)
51 63 if roles && tracker
52 64 workflows.find(:all,
53 65 :include => :new_status,
54 66 :conditions => { :role_id => roles.collect(&:id),
55 67 :tracker_id => tracker.id}).collect{ |w| w.new_status }.compact.sort
56 68 else
57 69 []
58 70 end
59 71 end
60 72
61 73 def new_status_allowed_to?(status, roles, tracker)
62 74 if status && roles && tracker
63 75 !workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => roles.collect(&:id), :tracker_id => tracker.id}).nil?
64 76 else
65 77 false
66 78 end
67 79 end
68 80
69 81 def <=>(status)
70 82 position <=> status.position
71 83 end
72 84
73 85 def to_s; name end
74 86
75 87 private
76 88 def check_integrity
77 89 raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
78 90 end
79 91 end
@@ -1,17 +1,22
1 1 <%= error_messages_for 'issue_status' %>
2 2
3 3 <div class="box">
4 4 <!--[form:issue_status]-->
5 5 <p><label for="issue_status_name"><%=l(:field_name)%><span class="required"> *</span></label>
6 6 <%= text_field 'issue_status', 'name' %></p>
7 7
8 <% if Issue.use_status_for_done_ratio? %>
9 <p><label for="issue_done_ratio"><%=l(:field_done_ratio)%></label>
10 <%= select 'issue_status', :default_done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
11 <% end %>
12
8 13 <p><label for="issue_status_is_closed"><%=l(:field_is_closed)%></label>
9 14 <%= check_box 'issue_status', 'is_closed' %></p>
10 15
11 16 <p><label for="issue_status_is_default"><%=l(:field_is_default)%></label>
12 17 <%= check_box 'issue_status', 'is_default' %></p>
13 18
14 19 <%= call_hook(:view_issue_statuses_form, :issue_status => @issue_status) %>
15 20
16 21 <!--[eoform:issue_status]-->
17 22 </div>
@@ -1,35 +1,42
1 1 <div class="contextual">
2 2 <%= link_to l(:label_issue_status_new), {:action => 'new'}, :class => 'icon icon-add' %>
3 <%= link_to(l(:label_update_issue_done_ratios), {:action => 'update_issue_done_ratio'}, :class => 'icon icon-multiple', :method => 'post', :confirm => l(:text_are_you_sure)) if Issue.use_status_for_done_ratio? %>
3 4 </div>
4 5
5 6 <h2><%=l(:label_issue_status_plural)%></h2>
6 7
7 8 <table class="list">
8 9 <thead><tr>
9 10 <th><%=l(:field_status)%></th>
11 <% if Issue.use_status_for_done_ratio? %>
12 <th><%=l(:field_done_ratio)%></th>
13 <% end %>
10 14 <th><%=l(:field_is_default)%></th>
11 15 <th><%=l(:field_is_closed)%></th>
12 16 <th><%=l(:button_sort)%></th>
13 17 <th></th>
14 18 </tr></thead>
15 19 <tbody>
16 20 <% for status in @issue_statuses %>
17 21 <tr class="<%= cycle("odd", "even") %>">
18 22 <td><%= link_to status.name, :action => 'edit', :id => status %></td>
23 <% if Issue.use_status_for_done_ratio? %>
24 <td align="center"><%= h status.default_done_ratio %></td>
25 <% end %>
19 26 <td align="center"><%= image_tag 'true.png' if status.is_default? %></td>
20 27 <td align="center"><%= image_tag 'true.png' if status.is_closed? %></td>
21 28 <td align="center" style="width:15%;"><%= reorder_links('issue_status', {:action => 'update', :id => status}) %></td>
22 29 <td class="buttons">
23 30 <%= link_to(l(:button_delete), { :action => 'destroy', :id => status },
24 31 :method => :post,
25 32 :confirm => l(:text_are_you_sure),
26 33 :class => 'icon icon-del') %>
27 34 </td>
28 35 </tr>
29 36 <% end %>
30 37 </tbody>
31 38 </table>
32 39
33 40 <p class="pagination"><%= pagination_links_full @issue_status_pages %></p>
34 41
35 42 <% html_title(l(:label_issue_status_plural)) -%>
@@ -1,43 +1,45
1 1 <% fields_for :issue, @issue, :builder => TabularFormBuilder do |f| %>
2 2
3 3 <div class="splitcontentleft">
4 4 <% if @issue.new_record? || @allowed_statuses.any? %>
5 5 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
6 6 <% else %>
7 7 <p><label><%= l(:field_status) %></label> <%= @issue.status.name %></p>
8 8 <% end %>
9 9
10 10 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p>
11 11 <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
12 12 <% unless @project.issue_categories.empty? %>
13 13 <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
14 14 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
15 15 l(:label_issue_category_new),
16 16 'category[name]',
17 17 {:controller => 'projects', :action => 'add_issue_category', :id => @project},
18 18 :title => l(:label_issue_category_new),
19 19 :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
20 20 <% end %>
21 21 <% unless @issue.assignable_versions.empty? %>
22 22 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
23 23 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
24 24 l(:label_version_new),
25 25 'version[name]',
26 26 {:controller => 'projects', :action => 'add_version', :id => @project},
27 27 :title => l(:label_version_new),
28 28 :tabindex => 200) if authorize_for('projects', 'add_version') %>
29 29 </p>
30 30 <% end %>
31 31 </div>
32 32
33 33 <div class="splitcontentright">
34 34 <p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p>
35 35 <p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p>
36 36 <p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p>
37 <% if Issue.use_field_for_done_ratio? %>
37 38 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
39 <% end %>
38 40 </div>
39 41
40 42 <div style="clear:both;"> </div>
41 43 <%= render :partial => 'form_custom_fields' %>
42 44
43 45 <% end %>
@@ -1,12 +1,14
1 1 <div class="attributes">
2 2 <div class="splitcontentleft">
3 3 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
4 4 <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
5 5 </div>
6 6 <div class="splitcontentright">
7 <% if Issue.use_field_for_done_ratio? %>
7 8 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
9 <% end %>
8 10 <% unless @issue.assignable_versions.empty? %>
9 11 <p><%= f.select :fixed_version_id, (@issue.assignable_versions.collect {|v| [v.name, v.id]}), :include_blank => true %></p>
10 12 <% end %>
11 13 </div>
12 14 </div>
@@ -1,62 +1,64
1 1 <h2><%= l(:label_bulk_edit_selected_issues) %></h2>
2 2
3 3 <ul><%= @issues.collect {|i| content_tag('li', link_to(h("#{i.tracker} ##{i.id}"), { :action => 'show', :id => i }) + h(": #{i.subject}")) }.join("\n") %></ul>
4 4
5 5 <% form_tag() do %>
6 6 <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
7 7 <div class="box">
8 8 <fieldset>
9 9 <legend><%= l(:label_change_properties) %></legend>
10 10 <p>
11 11 <label><%= l(:field_tracker) %>:
12 12 <%= select_tag('tracker_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@project.trackers, :id, :name)) %></label>
13 13 <% if @available_statuses.any? %>
14 14 <label><%= l(:field_status) %>:
15 15 <%= select_tag('status_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %></label>
16 16 <% end %>
17 17 </p>
18 18 <p>
19 19 <label><%= l(:field_priority) %>:
20 20 <%= select_tag('priority_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(IssuePriority.all, :id, :name)) %></label>
21 21 <label><%= l(:field_category) %>:
22 22 <%= select_tag('category_id', content_tag('option', l(:label_no_change_option), :value => '') +
23 23 content_tag('option', l(:label_none), :value => 'none') +
24 24 options_from_collection_for_select(@project.issue_categories, :id, :name)) %></label>
25 25 </p>
26 26 <p>
27 27 <label><%= l(:field_assigned_to) %>:
28 28 <%= select_tag('assigned_to_id', content_tag('option', l(:label_no_change_option), :value => '') +
29 29 content_tag('option', l(:label_nobody), :value => 'none') +
30 30 options_from_collection_for_select(@project.assignable_users, :id, :name)) %></label>
31 31 <label><%= l(:field_fixed_version) %>:
32 32 <%= select_tag('fixed_version_id', content_tag('option', l(:label_no_change_option), :value => '') +
33 33 content_tag('option', l(:label_none), :value => 'none') +
34 34 version_options_for_select(@project.shared_versions.open)) %></label>
35 35 </p>
36 36
37 37 <p>
38 38 <label><%= l(:field_start_date) %>:
39 39 <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %></label>
40 40 <label><%= l(:field_due_date) %>:
41 41 <%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %></label>
42 <% if Issue.use_field_for_done_ratio? %>
42 43 <label><%= l(:field_done_ratio) %>:
43 44 <%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label>
45 <% end %>
44 46 </p>
45 47
46 48 <% @custom_fields.each do |custom_field| %>
47 49 <p><label><%= h(custom_field.name) %></label>
48 50 <%= select_tag "custom_field_values[#{custom_field.id}]", options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values) %></label>
49 51 </p>
50 52 <% end %>
51 53
52 54 <%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %>
53 55 </fieldset>
54 56
55 57 <fieldset><legend><%= l(:field_notes) %></legend>
56 58 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
57 59 <%= wikitoolbar_for 'notes' %>
58 60 </fieldset>
59 61 </div>
60 62
61 63 <p><%= submit_tag l(:button_submit) %>
62 64 <% end %>
@@ -1,112 +1,113
1 1 <ul>
2 2 <%= call_hook(:view_issues_context_menu_start, {:issues => @issues, :can => @can, :back => @back }) %>
3 3
4 4 <% if !@issue.nil? -%>
5 5 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
6 6 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
7 7 <li class="folder">
8 8 <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
9 9 <ul>
10 10 <% @statuses.each do |s| -%>
11 11 <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'edit', :id => @issue, :issue => {:status_id => s}, :back_to => @back}, :method => :post,
12 12 :selected => (s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %></li>
13 13 <% end -%>
14 14 </ul>
15 15 </li>
16 16 <% else %>
17 17 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)},
18 18 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
19 19 <% end %>
20 20
21 21 <% unless @trackers.nil? %>
22 22 <li class="folder">
23 23 <a href="#" class="submenu"><%= l(:field_tracker) %></a>
24 24 <ul>
25 25 <% @trackers.each do |t| -%>
26 26 <li><%= context_menu_link t.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'tracker_id' => t, :back_to => @back}, :method => :post,
27 27 :selected => (@issue && t == @issue.tracker), :disabled => !@can[:edit] %></li>
28 28 <% end -%>
29 29 </ul>
30 30 </li>
31 31 <% end %>
32 32 <li class="folder">
33 33 <a href="#" class="submenu"><%= l(:field_priority) %></a>
34 34 <ul>
35 35 <% @priorities.each do |p| -%>
36 36 <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'priority_id' => p, :back_to => @back}, :method => :post,
37 37 :selected => (@issue && p == @issue.priority), :disabled => !@can[:edit] %></li>
38 38 <% end -%>
39 39 </ul>
40 40 </li>
41 41 <% unless @project.nil? || @project.shared_versions.open.empty? -%>
42 42 <li class="folder">
43 43 <a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
44 44 <ul>
45 45 <% @project.shared_versions.open.sort.each do |v| -%>
46 46 <li><%= context_menu_link format_version_name(v), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => v, :back_to => @back}, :method => :post,
47 47 :selected => (@issue && v == @issue.fixed_version), :disabled => !@can[:update] %></li>
48 48 <% end -%>
49 49 <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => 'none', :back_to => @back}, :method => :post,
50 50 :selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %></li>
51 51 </ul>
52 52 </li>
53 53 <% end %>
54 54 <% unless @assignables.nil? || @assignables.empty? -%>
55 55 <li class="folder">
56 56 <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
57 57 <ul>
58 58 <% @assignables.each do |u| -%>
59 59 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'assigned_to_id' => u, :back_to => @back}, :method => :post,
60 60 :selected => (@issue && u == @issue.assigned_to), :disabled => !@can[:update] %></li>
61 61 <% end -%>
62 62 <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'assigned_to_id' => 'none', :back_to => @back}, :method => :post,
63 63 :selected => (@issue && @issue.assigned_to.nil?), :disabled => !@can[:update] %></li>
64 64 </ul>
65 65 </li>
66 66 <% end %>
67 67 <% unless @project.nil? || @project.issue_categories.empty? -%>
68 68 <li class="folder">
69 69 <a href="#" class="submenu"><%= l(:field_category) %></a>
70 70 <ul>
71 71 <% @project.issue_categories.each do |u| -%>
72 72 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'category_id' => u, :back_to => @back}, :method => :post,
73 73 :selected => (@issue && u == @issue.category), :disabled => !@can[:update] %></li>
74 74 <% end -%>
75 75 <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'category_id' => 'none', :back_to => @back}, :method => :post,
76 76 :selected => (@issue && @issue.category.nil?), :disabled => !@can[:update] %></li>
77 77 </ul>
78 78 </li>
79 79 <% end -%>
80 <% if Issue.use_field_for_done_ratio? %>
80 81 <li class="folder">
81 82 <a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
82 83 <ul>
83 84 <% (0..10).map{|x|x*10}.each do |p| -%>
84 85 <li><%= context_menu_link "#{p}%", {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'done_ratio' => p, :back_to => @back}, :method => :post,
85 86 :selected => (@issue && p == @issue.done_ratio), :disabled => !@can[:edit] %></li>
86 87 <% end -%>
87 88 </ul>
88 89 </li>
89
90 <% end %>
90 91 <% if !@issue.nil? %>
91 92 <% if @can[:log_time] -%>
92 93 <li><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue},
93 94 :class => 'icon-time-add' %></li>
94 95 <% end %>
95 96 <% if User.current.logged? %>
96 97 <li><%= watcher_link(@issue, User.current) %></li>
97 98 <% end %>
98 99 <% end %>
99 100
100 101 <% if @issue.present? %>
101 102 <li><%= context_menu_link l(:button_duplicate), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue},
102 103 :class => 'icon-duplicate', :disabled => !@can[:copy] %></li>
103 104 <% end %>
104 105 <li><%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'move', :ids => @issues.collect(&:id), :copy_options => {:copy => 't'}},
105 106 :class => 'icon-copy', :disabled => !@can[:move] %></li>
106 107 <li><%= context_menu_link l(:button_move), {:controller => 'issues', :action => 'move', :ids => @issues.collect(&:id)},
107 108 :class => 'icon-move', :disabled => !@can[:move] %></li>
108 109 <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id)},
109 110 :method => :post, :confirm => l(:text_issues_destroy_confirmation), :class => 'icon-del', :disabled => !@can[:delete] %></li>
110 111
111 112 <%= call_hook(:view_issues_context_menu_end, {:issues => @issues, :can => @can, :back => @back }) %>
112 113 </ul>
@@ -1,27 +1,30
1 1 <% form_tag({:action => 'edit', :tab => 'issues'}) do %>
2 2
3 3 <div class="box tabular settings">
4 4 <p><label><%= l(:setting_cross_project_issue_relations) %></label>
5 5 <%= hidden_field_tag 'settings[cross_project_issue_relations]', 0 %>
6 6 <%= check_box_tag 'settings[cross_project_issue_relations]', 1, Setting.cross_project_issue_relations? %>
7 7 </p>
8 8
9 9 <p><label><%= l(:setting_display_subprojects_issues) %></label>
10 10 <%= hidden_field_tag 'settings[display_subprojects_issues]', 0 %>
11 11 <%= check_box_tag 'settings[display_subprojects_issues]', 1, Setting.display_subprojects_issues? %>
12 12 </p>
13 13
14 <p><label><%= l(:setting_issue_done_ratio) %></label>
15 <%= select_tag 'settings[issue_done_ratio]', options_for_select(Issue::DONE_RATIO_OPTIONS.collect {|i| [l(i.to_sym), i]}, Setting.issue_done_ratio) %></p>
16
14 17 <p><label><%= l(:setting_issues_export_limit) %></label>
15 18 <%= text_field_tag 'settings[issues_export_limit]', Setting.issues_export_limit, :size => 6 %></p>
16 19 </div>
17 20
18 21 <fieldset class="box settings"><legend><%= l(:setting_issue_list_default_columns) %></legend>
19 22 <%= hidden_field_tag 'settings[issue_list_default_columns][]', '' %>
20 23 <% Query.new.available_columns.each do |column| %>
21 24 <label><%= check_box_tag 'settings[issue_list_default_columns][]', column.name, Setting.issue_list_default_columns.include?(column.name.to_s) %>
22 25 <%= column.caption %></label><br />
23 26 <% end %>
24 27 </fieldset>
25 28
26 29 <%= submit_tag l(:button_save) %>
27 30 <% end %>
@@ -1,852 +1,859
1 1 en:
2 2 date:
3 3 formats:
4 4 # Use the strftime parameters for formats.
5 5 # When no format has been given, it uses default.
6 6 # You can provide other formats here if you like!
7 7 default: "%m/%d/%Y"
8 8 short: "%b %d"
9 9 long: "%B %d, %Y"
10 10
11 11 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
12 12 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
13 13
14 14 # Don't forget the nil at the beginning; there's no such thing as a 0th month
15 15 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
16 16 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
17 17 # Used in date_select and datime_select.
18 18 order: [ :year, :month, :day ]
19 19
20 20 time:
21 21 formats:
22 22 default: "%m/%d/%Y %I:%M %p"
23 23 time: "%I:%M %p"
24 24 short: "%d %b %H:%M"
25 25 long: "%B %d, %Y %H:%M"
26 26 am: "am"
27 27 pm: "pm"
28 28
29 29 datetime:
30 30 distance_in_words:
31 31 half_a_minute: "half a minute"
32 32 less_than_x_seconds:
33 33 one: "less than 1 second"
34 34 other: "less than {{count}} seconds"
35 35 x_seconds:
36 36 one: "1 second"
37 37 other: "{{count}} seconds"
38 38 less_than_x_minutes:
39 39 one: "less than a minute"
40 40 other: "less than {{count}} minutes"
41 41 x_minutes:
42 42 one: "1 minute"
43 43 other: "{{count}} minutes"
44 44 about_x_hours:
45 45 one: "about 1 hour"
46 46 other: "about {{count}} hours"
47 47 x_days:
48 48 one: "1 day"
49 49 other: "{{count}} days"
50 50 about_x_months:
51 51 one: "about 1 month"
52 52 other: "about {{count}} months"
53 53 x_months:
54 54 one: "1 month"
55 55 other: "{{count}} months"
56 56 about_x_years:
57 57 one: "about 1 year"
58 58 other: "about {{count}} years"
59 59 over_x_years:
60 60 one: "over 1 year"
61 61 other: "over {{count}} years"
62 62
63 63 number:
64 64 human:
65 65 format:
66 66 delimiter: ""
67 67 precision: 1
68 68 storage_units:
69 69 format: "%n %u"
70 70 units:
71 71 byte:
72 72 one: "Byte"
73 73 other: "Bytes"
74 74 kb: "KB"
75 75 mb: "MB"
76 76 gb: "GB"
77 77 tb: "TB"
78 78
79 79
80 80 # Used in array.to_sentence.
81 81 support:
82 82 array:
83 83 sentence_connector: "and"
84 84 skip_last_comma: false
85 85
86 86 activerecord:
87 87 errors:
88 88 messages:
89 89 inclusion: "is not included in the list"
90 90 exclusion: "is reserved"
91 91 invalid: "is invalid"
92 92 confirmation: "doesn't match confirmation"
93 93 accepted: "must be accepted"
94 94 empty: "can't be empty"
95 95 blank: "can't be blank"
96 96 too_long: "is too long (maximum is {{count}} characters)"
97 97 too_short: "is too short (minimum is {{count}} characters)"
98 98 wrong_length: "is the wrong length (should be {{count}} characters)"
99 99 taken: "has already been taken"
100 100 not_a_number: "is not a number"
101 101 not_a_date: "is not a valid date"
102 102 greater_than: "must be greater than {{count}}"
103 103 greater_than_or_equal_to: "must be greater than or equal to {{count}}"
104 104 equal_to: "must be equal to {{count}}"
105 105 less_than: "must be less than {{count}}"
106 106 less_than_or_equal_to: "must be less than or equal to {{count}}"
107 107 odd: "must be odd"
108 108 even: "must be even"
109 109 greater_than_start_date: "must be greater than start date"
110 110 not_same_project: "doesn't belong to the same project"
111 111 circular_dependency: "This relation would create a circular dependency"
112 112
113 113 actionview_instancetag_blank_option: Please select
114 114
115 115 general_text_No: 'No'
116 116 general_text_Yes: 'Yes'
117 117 general_text_no: 'no'
118 118 general_text_yes: 'yes'
119 119 general_lang_name: 'English'
120 120 general_csv_separator: ','
121 121 general_csv_decimal_separator: '.'
122 122 general_csv_encoding: ISO-8859-1
123 123 general_pdf_encoding: ISO-8859-1
124 124 general_first_day_of_week: '7'
125 125
126 126 notice_account_updated: Account was successfully updated.
127 127 notice_account_invalid_creditentials: Invalid user or password
128 128 notice_account_password_updated: Password was successfully updated.
129 129 notice_account_wrong_password: Wrong password
130 130 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
131 131 notice_account_unknown_email: Unknown user.
132 132 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
133 133 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
134 134 notice_account_activated: Your account has been activated. You can now log in.
135 135 notice_successful_create: Successful creation.
136 136 notice_successful_update: Successful update.
137 137 notice_successful_delete: Successful deletion.
138 138 notice_successful_connection: Successful connection.
139 139 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
140 140 notice_locking_conflict: Data has been updated by another user.
141 141 notice_not_authorized: You are not authorized to access this page.
142 142 notice_email_sent: "An email was sent to {{value}}"
143 143 notice_email_error: "An error occurred while sending mail ({{value}})"
144 144 notice_feeds_access_key_reseted: Your RSS access key was reset.
145 145 notice_failed_to_save_issues: "Failed to save {{count}} issue(s) on {{total}} selected: {{ids}}."
146 146 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
147 147 notice_account_pending: "Your account was created and is now pending administrator approval."
148 148 notice_default_data_loaded: Default configuration successfully loaded.
149 149 notice_unable_delete_version: Unable to delete version.
150 notice_issue_done_ratios_updated: Issue done ratios updated.
150 151
151 152 error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}"
152 153 error_scm_not_found: "The entry or revision was not found in the repository."
153 154 error_scm_command_failed: "An error occurred when trying to access the repository: {{value}}"
154 155 error_scm_annotate: "The entry does not exist or can not be annotated."
155 156 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
156 157 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
157 158 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
158 159 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened'
159 160 error_can_not_archive_project: This project can not be archived
160
161 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
162
161 163 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
162 164
163 165 mail_subject_lost_password: "Your {{value}} password"
164 166 mail_body_lost_password: 'To change your password, click on the following link:'
165 167 mail_subject_register: "Your {{value}} account activation"
166 168 mail_body_register: 'To activate your account, click on the following link:'
167 169 mail_body_account_information_external: "You can use your {{value}} account to log in."
168 170 mail_body_account_information: Your account information
169 171 mail_subject_account_activation_request: "{{value}} account activation request"
170 172 mail_body_account_activation_request: "A new user ({{value}}) has registered. The account is pending your approval:"
171 173 mail_subject_reminder: "{{count}} issue(s) due in the next days"
172 174 mail_body_reminder: "{{count}} issue(s) that are assigned to you are due in the next {{days}} days:"
173 175 mail_subject_wiki_content_added: "'{{page}}' wiki page has been added"
174 176 mail_body_wiki_content_added: "The '{{page}}' wiki page has been added by {{author}}."
175 177 mail_subject_wiki_content_updated: "'{{page}}' wiki page has been updated"
176 178 mail_body_wiki_content_updated: "The '{{page}}' wiki page has been updated by {{author}}."
177 179
178 180 gui_validation_error: 1 error
179 181 gui_validation_error_plural: "{{count}} errors"
180 182
181 183 field_name: Name
182 184 field_description: Description
183 185 field_summary: Summary
184 186 field_is_required: Required
185 187 field_firstname: Firstname
186 188 field_lastname: Lastname
187 189 field_mail: Email
188 190 field_filename: File
189 191 field_filesize: Size
190 192 field_downloads: Downloads
191 193 field_author: Author
192 194 field_created_on: Created
193 195 field_updated_on: Updated
194 196 field_field_format: Format
195 197 field_is_for_all: For all projects
196 198 field_possible_values: Possible values
197 199 field_regexp: Regular expression
198 200 field_min_length: Minimum length
199 201 field_max_length: Maximum length
200 202 field_value: Value
201 203 field_category: Category
202 204 field_title: Title
203 205 field_project: Project
204 206 field_issue: Issue
205 207 field_status: Status
206 208 field_notes: Notes
207 209 field_is_closed: Issue closed
208 210 field_is_default: Default value
209 211 field_tracker: Tracker
210 212 field_subject: Subject
211 213 field_due_date: Due date
212 214 field_assigned_to: Assigned to
213 215 field_priority: Priority
214 216 field_fixed_version: Target version
215 217 field_user: User
216 218 field_role: Role
217 219 field_homepage: Homepage
218 220 field_is_public: Public
219 221 field_parent: Subproject of
220 222 field_is_in_chlog: Issues displayed in changelog
221 223 field_is_in_roadmap: Issues displayed in roadmap
222 224 field_login: Login
223 225 field_mail_notification: Email notifications
224 226 field_admin: Administrator
225 227 field_last_login_on: Last connection
226 228 field_language: Language
227 229 field_effective_date: Date
228 230 field_password: Password
229 231 field_new_password: New password
230 232 field_password_confirmation: Confirmation
231 233 field_version: Version
232 234 field_type: Type
233 235 field_host: Host
234 236 field_port: Port
235 237 field_account: Account
236 238 field_base_dn: Base DN
237 239 field_attr_login: Login attribute
238 240 field_attr_firstname: Firstname attribute
239 241 field_attr_lastname: Lastname attribute
240 242 field_attr_mail: Email attribute
241 243 field_onthefly: On-the-fly user creation
242 244 field_start_date: Start
243 245 field_done_ratio: % Done
244 246 field_auth_source: Authentication mode
245 247 field_hide_mail: Hide my email address
246 248 field_comments: Comment
247 249 field_url: URL
248 250 field_start_page: Start page
249 251 field_subproject: Subproject
250 252 field_hours: Hours
251 253 field_activity: Activity
252 254 field_spent_on: Date
253 255 field_identifier: Identifier
254 256 field_is_filter: Used as a filter
255 257 field_issue_to: Related issue
256 258 field_delay: Delay
257 259 field_assignable: Issues can be assigned to this role
258 260 field_redirect_existing_links: Redirect existing links
259 261 field_estimated_hours: Estimated time
260 262 field_column_names: Columns
261 263 field_time_zone: Time zone
262 264 field_searchable: Searchable
263 265 field_default_value: Default value
264 266 field_comments_sorting: Display comments
265 267 field_parent_title: Parent page
266 268 field_editable: Editable
267 269 field_watcher: Watcher
268 270 field_identity_url: OpenID URL
269 271 field_content: Content
270 272 field_group_by: Group results by
271 273 field_sharing: Sharing
272 274
273 275 setting_app_title: Application title
274 276 setting_app_subtitle: Application subtitle
275 277 setting_welcome_text: Welcome text
276 278 setting_default_language: Default language
277 279 setting_login_required: Authentication required
278 280 setting_self_registration: Self-registration
279 281 setting_attachment_max_size: Attachment max. size
280 282 setting_issues_export_limit: Issues export limit
281 283 setting_mail_from: Emission email address
282 284 setting_bcc_recipients: Blind carbon copy recipients (bcc)
283 285 setting_plain_text_mail: Plain text mail (no HTML)
284 286 setting_host_name: Host name and path
285 287 setting_text_formatting: Text formatting
286 288 setting_wiki_compression: Wiki history compression
287 289 setting_feeds_limit: Feed content limit
288 290 setting_default_projects_public: New projects are public by default
289 291 setting_autofetch_changesets: Autofetch commits
290 292 setting_sys_api_enabled: Enable WS for repository management
291 293 setting_commit_ref_keywords: Referencing keywords
292 294 setting_commit_fix_keywords: Fixing keywords
293 295 setting_autologin: Autologin
294 296 setting_date_format: Date format
295 297 setting_time_format: Time format
296 298 setting_cross_project_issue_relations: Allow cross-project issue relations
297 299 setting_issue_list_default_columns: Default columns displayed on the issue list
298 300 setting_repositories_encodings: Repositories encodings
299 301 setting_commit_logs_encoding: Commit messages encoding
300 302 setting_emails_footer: Emails footer
301 303 setting_protocol: Protocol
302 304 setting_per_page_options: Objects per page options
303 305 setting_user_format: Users display format
304 306 setting_activity_days_default: Days displayed on project activity
305 307 setting_display_subprojects_issues: Display subprojects issues on main projects by default
306 308 setting_enabled_scm: Enabled SCM
307 309 setting_mail_handler_api_enabled: Enable WS for incoming emails
308 310 setting_mail_handler_api_key: API key
309 311 setting_sequential_project_identifiers: Generate sequential project identifiers
310 312 setting_gravatar_enabled: Use Gravatar user icons
311 313 setting_gravatar_default: Default Gravatar image
314 setting_issue_done_ratio: Calculate the issue done ratio with
312 315 setting_diff_max_lines_displayed: Max number of diff lines displayed
313 316 setting_file_max_size_displayed: Max size of text files displayed inline
314 317 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
315 318 setting_openid: Allow OpenID login and registration
316 319 setting_password_min_length: Minimum password length
317 320 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
318 321 setting_default_projects_modules: Default enabled modules for new projects
319 322
320 323 permission_add_project: Create project
321 324 permission_edit_project: Edit project
322 325 permission_select_project_modules: Select project modules
323 326 permission_manage_members: Manage members
324 327 permission_manage_versions: Manage versions
325 328 permission_manage_categories: Manage issue categories
326 329 permission_add_issues: Add issues
327 330 permission_edit_issues: Edit issues
328 331 permission_manage_issue_relations: Manage issue relations
329 332 permission_add_issue_notes: Add notes
330 333 permission_edit_issue_notes: Edit notes
331 334 permission_edit_own_issue_notes: Edit own notes
332 335 permission_move_issues: Move issues
333 336 permission_delete_issues: Delete issues
334 337 permission_manage_public_queries: Manage public queries
335 338 permission_save_queries: Save queries
336 339 permission_view_gantt: View gantt chart
337 340 permission_view_calendar: View calendar
338 341 permission_view_issue_watchers: View watchers list
339 342 permission_add_issue_watchers: Add watchers
340 343 permission_delete_issue_watchers: Delete watchers
341 344 permission_log_time: Log spent time
342 345 permission_view_time_entries: View spent time
343 346 permission_edit_time_entries: Edit time logs
344 347 permission_edit_own_time_entries: Edit own time logs
345 348 permission_manage_news: Manage news
346 349 permission_comment_news: Comment news
347 350 permission_manage_documents: Manage documents
348 351 permission_view_documents: View documents
349 352 permission_manage_files: Manage files
350 353 permission_view_files: View files
351 354 permission_manage_wiki: Manage wiki
352 355 permission_rename_wiki_pages: Rename wiki pages
353 356 permission_delete_wiki_pages: Delete wiki pages
354 357 permission_view_wiki_pages: View wiki
355 358 permission_view_wiki_edits: View wiki history
356 359 permission_edit_wiki_pages: Edit wiki pages
357 360 permission_delete_wiki_pages_attachments: Delete attachments
358 361 permission_protect_wiki_pages: Protect wiki pages
359 362 permission_manage_repository: Manage repository
360 363 permission_browse_repository: Browse repository
361 364 permission_view_changesets: View changesets
362 365 permission_commit_access: Commit access
363 366 permission_manage_boards: Manage boards
364 367 permission_view_messages: View messages
365 368 permission_add_messages: Post messages
366 369 permission_edit_messages: Edit messages
367 370 permission_edit_own_messages: Edit own messages
368 371 permission_delete_messages: Delete messages
369 372 permission_delete_own_messages: Delete own messages
370 373
371 374 project_module_issue_tracking: Issue tracking
372 375 project_module_time_tracking: Time tracking
373 376 project_module_news: News
374 377 project_module_documents: Documents
375 378 project_module_files: Files
376 379 project_module_wiki: Wiki
377 380 project_module_repository: Repository
378 381 project_module_boards: Boards
379 382
380 383 label_user: User
381 384 label_user_plural: Users
382 385 label_user_new: New user
383 386 label_user_anonymous: Anonymous
384 387 label_project: Project
385 388 label_project_new: New project
386 389 label_project_plural: Projects
387 390 label_x_projects:
388 391 zero: no projects
389 392 one: 1 project
390 393 other: "{{count}} projects"
391 394 label_project_all: All Projects
392 395 label_project_latest: Latest projects
393 396 label_issue: Issue
394 397 label_issue_new: New issue
395 398 label_issue_plural: Issues
396 399 label_issue_view_all: View all issues
397 400 label_issues_by: "Issues by {{value}}"
398 401 label_issue_added: Issue added
399 402 label_issue_updated: Issue updated
400 403 label_document: Document
401 404 label_document_new: New document
402 405 label_document_plural: Documents
403 406 label_document_added: Document added
404 407 label_role: Role
405 408 label_role_plural: Roles
406 409 label_role_new: New role
407 410 label_role_and_permissions: Roles and permissions
408 411 label_member: Member
409 412 label_member_new: New member
410 413 label_member_plural: Members
411 414 label_tracker: Tracker
412 415 label_tracker_plural: Trackers
413 416 label_tracker_new: New tracker
414 417 label_workflow: Workflow
415 418 label_issue_status: Issue status
416 419 label_issue_status_plural: Issue statuses
417 420 label_issue_status_new: New status
418 421 label_issue_category: Issue category
419 422 label_issue_category_plural: Issue categories
420 423 label_issue_category_new: New category
421 424 label_custom_field: Custom field
422 425 label_custom_field_plural: Custom fields
423 426 label_custom_field_new: New custom field
424 427 label_enumerations: Enumerations
425 428 label_enumeration_new: New value
426 429 label_information: Information
427 430 label_information_plural: Information
428 431 label_please_login: Please log in
429 432 label_register: Register
430 433 label_login_with_open_id_option: or login with OpenID
431 434 label_password_lost: Lost password
432 435 label_home: Home
433 436 label_my_page: My page
434 437 label_my_account: My account
435 438 label_my_projects: My projects
436 439 label_administration: Administration
437 440 label_login: Sign in
438 441 label_logout: Sign out
439 442 label_help: Help
440 443 label_reported_issues: Reported issues
441 444 label_assigned_to_me_issues: Issues assigned to me
442 445 label_last_login: Last connection
443 446 label_registered_on: Registered on
444 447 label_activity: Activity
445 448 label_overall_activity: Overall activity
446 449 label_user_activity: "{{value}}'s activity"
447 450 label_new: New
448 451 label_logged_as: Logged in as
449 452 label_environment: Environment
450 453 label_authentication: Authentication
451 454 label_auth_source: Authentication mode
452 455 label_auth_source_new: New authentication mode
453 456 label_auth_source_plural: Authentication modes
454 457 label_subproject_plural: Subprojects
455 458 label_and_its_subprojects: "{{value}} and its subprojects"
456 459 label_min_max_length: Min - Max length
457 460 label_list: List
458 461 label_date: Date
459 462 label_integer: Integer
460 463 label_float: Float
461 464 label_boolean: Boolean
462 465 label_string: Text
463 466 label_text: Long text
464 467 label_attribute: Attribute
465 468 label_attribute_plural: Attributes
466 469 label_download: "{{count}} Download"
467 470 label_download_plural: "{{count}} Downloads"
468 471 label_no_data: No data to display
469 472 label_change_status: Change status
470 473 label_history: History
471 474 label_attachment: File
472 475 label_attachment_new: New file
473 476 label_attachment_delete: Delete file
474 477 label_attachment_plural: Files
475 478 label_file_added: File added
476 479 label_report: Report
477 480 label_report_plural: Reports
478 481 label_news: News
479 482 label_news_new: Add news
480 483 label_news_plural: News
481 484 label_news_latest: Latest news
482 485 label_news_view_all: View all news
483 486 label_news_added: News added
484 487 label_change_log: Change log
485 488 label_settings: Settings
486 489 label_overview: Overview
487 490 label_version: Version
488 491 label_version_new: New version
489 492 label_version_plural: Versions
490 493 label_confirmation: Confirmation
491 494 label_export_to: 'Also available in:'
492 495 label_read: Read...
493 496 label_public_projects: Public projects
494 497 label_open_issues: open
495 498 label_open_issues_plural: open
496 499 label_closed_issues: closed
497 500 label_closed_issues_plural: closed
498 501 label_x_open_issues_abbr_on_total:
499 502 zero: 0 open / {{total}}
500 503 one: 1 open / {{total}}
501 504 other: "{{count}} open / {{total}}"
502 505 label_x_open_issues_abbr:
503 506 zero: 0 open
504 507 one: 1 open
505 508 other: "{{count}} open"
506 509 label_x_closed_issues_abbr:
507 510 zero: 0 closed
508 511 one: 1 closed
509 512 other: "{{count}} closed"
510 513 label_total: Total
511 514 label_permissions: Permissions
512 515 label_current_status: Current status
513 516 label_new_statuses_allowed: New statuses allowed
514 517 label_all: all
515 518 label_none: none
516 519 label_nobody: nobody
517 520 label_next: Next
518 521 label_previous: Previous
519 522 label_used_by: Used by
520 523 label_details: Details
521 524 label_add_note: Add a note
522 525 label_per_page: Per page
523 526 label_calendar: Calendar
524 527 label_months_from: months from
525 528 label_gantt: Gantt
526 529 label_internal: Internal
527 530 label_last_changes: "last {{count}} changes"
528 531 label_change_view_all: View all changes
529 532 label_personalize_page: Personalize this page
530 533 label_comment: Comment
531 534 label_comment_plural: Comments
532 535 label_x_comments:
533 536 zero: no comments
534 537 one: 1 comment
535 538 other: "{{count}} comments"
536 539 label_comment_add: Add a comment
537 540 label_comment_added: Comment added
538 541 label_comment_delete: Delete comments
539 542 label_query: Custom query
540 543 label_query_plural: Custom queries
541 544 label_query_new: New query
542 545 label_filter_add: Add filter
543 546 label_filter_plural: Filters
544 547 label_equals: is
545 548 label_not_equals: is not
546 549 label_in_less_than: in less than
547 550 label_in_more_than: in more than
548 551 label_greater_or_equal: '>='
549 552 label_less_or_equal: '<='
550 553 label_in: in
551 554 label_today: today
552 555 label_all_time: all time
553 556 label_yesterday: yesterday
554 557 label_this_week: this week
555 558 label_last_week: last week
556 559 label_last_n_days: "last {{count}} days"
557 560 label_this_month: this month
558 561 label_last_month: last month
559 562 label_this_year: this year
560 563 label_date_range: Date range
561 564 label_less_than_ago: less than days ago
562 565 label_more_than_ago: more than days ago
563 566 label_ago: days ago
564 567 label_contains: contains
565 568 label_not_contains: doesn't contain
566 569 label_day_plural: days
567 570 label_repository: Repository
568 571 label_repository_plural: Repositories
569 572 label_browse: Browse
570 573 label_modification: "{{count}} change"
571 574 label_modification_plural: "{{count}} changes"
572 575 label_branch: Branch
573 576 label_tag: Tag
574 577 label_revision: Revision
575 578 label_revision_plural: Revisions
576 579 label_associated_revisions: Associated revisions
577 580 label_added: added
578 581 label_modified: modified
579 582 label_copied: copied
580 583 label_renamed: renamed
581 584 label_deleted: deleted
582 585 label_latest_revision: Latest revision
583 586 label_latest_revision_plural: Latest revisions
584 587 label_view_revisions: View revisions
585 588 label_view_all_revisions: View all revisions
586 589 label_max_size: Maximum size
587 590 label_sort_highest: Move to top
588 591 label_sort_higher: Move up
589 592 label_sort_lower: Move down
590 593 label_sort_lowest: Move to bottom
591 594 label_roadmap: Roadmap
592 595 label_roadmap_due_in: "Due in {{value}}"
593 596 label_roadmap_overdue: "{{value}} late"
594 597 label_roadmap_no_issues: No issues for this version
595 598 label_search: Search
596 599 label_result_plural: Results
597 600 label_all_words: All words
598 601 label_wiki: Wiki
599 602 label_wiki_edit: Wiki edit
600 603 label_wiki_edit_plural: Wiki edits
601 604 label_wiki_page: Wiki page
602 605 label_wiki_page_plural: Wiki pages
603 606 label_index_by_title: Index by title
604 607 label_index_by_date: Index by date
605 608 label_current_version: Current version
606 609 label_preview: Preview
607 610 label_feed_plural: Feeds
608 611 label_changes_details: Details of all changes
609 612 label_issue_tracking: Issue tracking
610 613 label_spent_time: Spent time
611 614 label_f_hour: "{{value}} hour"
612 615 label_f_hour_plural: "{{value}} hours"
613 616 label_time_tracking: Time tracking
614 617 label_change_plural: Changes
615 618 label_statistics: Statistics
616 619 label_commits_per_month: Commits per month
617 620 label_commits_per_author: Commits per author
618 621 label_view_diff: View differences
619 622 label_diff_inline: inline
620 623 label_diff_side_by_side: side by side
621 624 label_options: Options
622 625 label_copy_workflow_from: Copy workflow from
623 626 label_permissions_report: Permissions report
624 627 label_watched_issues: Watched issues
625 628 label_related_issues: Related issues
626 629 label_applied_status: Applied status
627 630 label_loading: Loading...
628 631 label_relation_new: New relation
629 632 label_relation_delete: Delete relation
630 633 label_relates_to: related to
631 634 label_duplicates: duplicates
632 635 label_duplicated_by: duplicated by
633 636 label_blocks: blocks
634 637 label_blocked_by: blocked by
635 638 label_precedes: precedes
636 639 label_follows: follows
637 640 label_end_to_start: end to start
638 641 label_end_to_end: end to end
639 642 label_start_to_start: start to start
640 643 label_start_to_end: start to end
641 644 label_stay_logged_in: Stay logged in
642 645 label_disabled: disabled
643 646 label_show_completed_versions: Show completed versions
644 647 label_me: me
645 648 label_board: Forum
646 649 label_board_new: New forum
647 650 label_board_plural: Forums
648 651 label_topic_plural: Topics
649 652 label_message_plural: Messages
650 653 label_message_last: Last message
651 654 label_message_new: New message
652 655 label_message_posted: Message added
653 656 label_reply_plural: Replies
654 657 label_send_information: Send account information to the user
655 658 label_year: Year
656 659 label_month: Month
657 660 label_week: Week
658 661 label_date_from: From
659 662 label_date_to: To
660 663 label_language_based: Based on user's language
661 664 label_sort_by: "Sort by {{value}}"
662 665 label_send_test_email: Send a test email
663 666 label_feeds_access_key_created_on: "RSS access key created {{value}} ago"
664 667 label_module_plural: Modules
665 668 label_added_time_by: "Added by {{author}} {{age}} ago"
666 669 label_updated_time_by: "Updated by {{author}} {{age}} ago"
667 670 label_updated_time: "Updated {{value}} ago"
668 671 label_jump_to_a_project: Jump to a project...
669 672 label_file_plural: Files
670 673 label_changeset_plural: Changesets
671 674 label_default_columns: Default columns
672 675 label_no_change_option: (No change)
673 676 label_bulk_edit_selected_issues: Bulk edit selected issues
674 677 label_theme: Theme
675 678 label_default: Default
676 679 label_search_titles_only: Search titles only
677 680 label_user_mail_option_all: "For any event on all my projects"
678 681 label_user_mail_option_selected: "For any event on the selected projects only..."
679 682 label_user_mail_option_none: "Only for things I watch or I'm involved in"
680 683 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
681 684 label_registration_activation_by_email: account activation by email
682 685 label_registration_manual_activation: manual account activation
683 686 label_registration_automatic_activation: automatic account activation
684 687 label_display_per_page: "Per page: {{value}}"
685 688 label_age: Age
686 689 label_change_properties: Change properties
687 690 label_general: General
688 691 label_more: More
689 692 label_scm: SCM
690 693 label_plugins: Plugins
691 694 label_ldap_authentication: LDAP authentication
692 695 label_downloads_abbr: D/L
693 696 label_optional_description: Optional description
694 697 label_add_another_file: Add another file
695 698 label_preferences: Preferences
696 699 label_chronological_order: In chronological order
697 700 label_reverse_chronological_order: In reverse chronological order
698 701 label_planning: Planning
699 702 label_incoming_emails: Incoming emails
700 703 label_generate_key: Generate a key
701 704 label_issue_watchers: Watchers
702 705 label_example: Example
703 706 label_display: Display
704 707 label_sort: Sort
705 708 label_ascending: Ascending
706 709 label_descending: Descending
707 710 label_date_from_to: From {{start}} to {{end}}
708 711 label_wiki_content_added: Wiki page added
709 712 label_wiki_content_updated: Wiki page updated
710 713 label_group: Group
711 714 label_group_plural: Groups
712 715 label_group_new: New group
713 716 label_time_entry_plural: Spent time
714 717 label_version_sharing_none: Not shared
715 718 label_version_sharing_descendants: With subprojects
716 719 label_version_sharing_hierarchy: With project hierarchy
717 720 label_version_sharing_tree: With project tree
718 721 label_version_sharing_system: With all projects
722 label_update_issue_done_ratios: Update issue done ratios
719 723
720 724 button_login: Login
721 725 button_submit: Submit
722 726 button_save: Save
723 727 button_check_all: Check all
724 728 button_uncheck_all: Uncheck all
725 729 button_delete: Delete
726 730 button_create: Create
727 731 button_create_and_continue: Create and continue
728 732 button_test: Test
729 733 button_edit: Edit
730 734 button_add: Add
731 735 button_change: Change
732 736 button_apply: Apply
733 737 button_clear: Clear
734 738 button_lock: Lock
735 739 button_unlock: Unlock
736 740 button_download: Download
737 741 button_list: List
738 742 button_view: View
739 743 button_move: Move
740 744 button_move_and_follow: Move and follow
741 745 button_back: Back
742 746 button_cancel: Cancel
743 747 button_activate: Activate
744 748 button_sort: Sort
745 749 button_log_time: Log time
746 750 button_rollback: Rollback to this version
747 751 button_watch: Watch
748 752 button_unwatch: Unwatch
749 753 button_reply: Reply
750 754 button_archive: Archive
751 755 button_unarchive: Unarchive
752 756 button_reset: Reset
753 757 button_rename: Rename
754 758 button_change_password: Change password
755 759 button_copy: Copy
756 760 button_copy_and_follow: Copy and follow
757 761 button_annotate: Annotate
758 762 button_update: Update
759 763 button_configure: Configure
760 764 button_quote: Quote
761 765 button_duplicate: Duplicate
762 766
763 767 status_active: active
764 768 status_registered: registered
765 769 status_locked: locked
766 770
767 771 version_status_open: open
768 772 version_status_locked: locked
769 773 version_status_closed: closed
770 774
771 775 field_active: Active
772 776
773 777 text_select_mail_notifications: Select actions for which email notifications should be sent.
774 778 text_regexp_info: eg. ^[A-Z0-9]+$
775 779 text_min_max_length_info: 0 means no restriction
776 780 text_project_destroy_confirmation: Are you sure you want to delete this project and related data ?
777 781 text_subprojects_destroy_warning: "Its subproject(s): {{value}} will be also deleted."
778 782 text_workflow_edit: Select a role and a tracker to edit the workflow
779 783 text_are_you_sure: Are you sure ?
780 784 text_journal_changed: "{{label}} changed from {{old}} to {{new}}"
781 785 text_journal_set_to: "{{label}} set to {{value}}"
782 786 text_journal_deleted: "{{label}} deleted ({{old}})"
783 787 text_journal_added: "{{label}} {{value}} added"
784 788 text_tip_task_begin_day: task beginning this day
785 789 text_tip_task_end_day: task ending this day
786 790 text_tip_task_begin_end_day: task beginning and ending this day
787 791 text_project_identifier_info: 'Only lower case letters (a-z), numbers and dashes are allowed.<br />Once saved, the identifier can not be changed.'
788 792 text_caracters_maximum: "{{count}} characters maximum."
789 793 text_caracters_minimum: "Must be at least {{count}} characters long."
790 794 text_length_between: "Length between {{min}} and {{max}} characters."
791 795 text_tracker_no_workflow: No workflow defined for this tracker
792 796 text_unallowed_characters: Unallowed characters
793 797 text_comma_separated: Multiple values allowed (comma separated).
794 798 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
795 799 text_issue_added: "Issue {{id}} has been reported by {{author}}."
796 800 text_issue_updated: "Issue {{id}} has been updated by {{author}}."
797 801 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ?
798 802 text_issue_category_destroy_question: "Some issues ({{count}}) are assigned to this category. What do you want to do ?"
799 803 text_issue_category_destroy_assignments: Remove category assignments
800 804 text_issue_category_reassign_to: Reassign issues to this category
801 805 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)."
802 806 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."
803 807 text_load_default_configuration: Load the default configuration
804 808 text_status_changed_by_changeset: "Applied in changeset {{value}}."
805 809 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
806 810 text_select_project_modules: 'Select modules to enable for this project:'
807 811 text_default_administrator_account_changed: Default administrator account changed
808 812 text_file_repository_writable: Attachments directory writable
809 813 text_plugin_assets_writable: Plugin assets directory writable
810 814 text_rmagick_available: RMagick available (optional)
811 815 text_destroy_time_entries_question: "{{hours}} hours were reported on the issues you are about to delete. What do you want to do ?"
812 816 text_destroy_time_entries: Delete reported hours
813 817 text_assign_time_entries_to_project: Assign reported hours to the project
814 818 text_reassign_time_entries: 'Reassign reported hours to this issue:'
815 819 text_user_wrote: "{{value}} wrote:"
816 820 text_enumeration_destroy_question: "{{count}} objects are assigned to this value."
817 821 text_enumeration_category_reassign_to: 'Reassign them to this value:'
818 822 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
819 823 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."
820 824 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
821 825 text_custom_field_possible_values_info: 'One line for each value'
822 826 text_wiki_page_destroy_question: "This page has {{descendants}} child page(s) and descendant(s). What do you want to do?"
823 827 text_wiki_page_nullify_children: "Keep child pages as root pages"
824 828 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
825 829 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
826 830
827 831 default_role_manager: Manager
828 832 default_role_developper: Developer
829 833 default_role_reporter: Reporter
830 834 default_tracker_bug: Bug
831 835 default_tracker_feature: Feature
832 836 default_tracker_support: Support
833 837 default_issue_status_new: New
834 838 default_issue_status_in_progress: In Progress
835 839 default_issue_status_resolved: Resolved
836 840 default_issue_status_feedback: Feedback
837 841 default_issue_status_closed: Closed
838 842 default_issue_status_rejected: Rejected
839 843 default_doc_category_user: User documentation
840 844 default_doc_category_tech: Technical documentation
841 845 default_priority_low: Low
842 846 default_priority_normal: Normal
843 847 default_priority_high: High
844 848 default_priority_urgent: Urgent
845 849 default_priority_immediate: Immediate
846 850 default_activity_design: Design
847 851 default_activity_development: Development
848 852
849 853 enumeration_issue_priorities: Issue priorities
850 854 enumeration_doc_categories: Document categories
851 855 enumeration_activities: Activities (time tracking)
852 856 enumeration_system_activity: System Activity
857
858 issue_field: Use the issue field
859 issue_status: Use the issue status
@@ -1,172 +1,174
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18
19 19 # DO NOT MODIFY THIS FILE !!!
20 20 # Settings can be defined through the application in Admin -> Settings
21 21
22 22 app_title:
23 23 default: Redmine
24 24 app_subtitle:
25 25 default: Project management
26 26 welcome_text:
27 27 default:
28 28 login_required:
29 29 default: 0
30 30 self_registration:
31 31 default: '2'
32 32 lost_password:
33 33 default: 1
34 34 password_min_length:
35 35 format: int
36 36 default: 4
37 37 attachment_max_size:
38 38 format: int
39 39 default: 5120
40 40 issues_export_limit:
41 41 format: int
42 42 default: 500
43 43 activity_days_default:
44 44 format: int
45 45 default: 30
46 46 per_page_options:
47 47 default: '25,50,100'
48 48 mail_from:
49 49 default: redmine@example.net
50 50 bcc_recipients:
51 51 default: 1
52 52 plain_text_mail:
53 53 default: 0
54 54 text_formatting:
55 55 default: textile
56 56 wiki_compression:
57 57 default: ""
58 58 default_language:
59 59 default: en
60 60 host_name:
61 61 default: localhost:3000
62 62 protocol:
63 63 default: http
64 64 feeds_limit:
65 65 format: int
66 66 default: 15
67 67 # Maximum size of files that can be displayed
68 68 # inline through the file viewer (in KB)
69 69 file_max_size_displayed:
70 70 format: int
71 71 default: 512
72 72 diff_max_lines_displayed:
73 73 format: int
74 74 default: 1500
75 75 enabled_scm:
76 76 serialized: true
77 77 default:
78 78 - Subversion
79 79 - Darcs
80 80 - Mercurial
81 81 - Cvs
82 82 - Bazaar
83 83 - Git
84 84 autofetch_changesets:
85 85 default: 1
86 86 sys_api_enabled:
87 87 default: 0
88 88 commit_ref_keywords:
89 89 default: 'refs,references,IssueID'
90 90 commit_fix_keywords:
91 91 default: 'fixes,closes'
92 92 commit_fix_status_id:
93 93 format: int
94 94 default: 0
95 95 commit_fix_done_ratio:
96 96 default: 100
97 97 # autologin duration in days
98 98 # 0 means autologin is disabled
99 99 autologin:
100 100 format: int
101 101 default: 0
102 102 # date format
103 103 date_format:
104 104 default: ''
105 105 time_format:
106 106 default: ''
107 107 user_format:
108 108 default: :firstname_lastname
109 109 format: symbol
110 110 cross_project_issue_relations:
111 111 default: 0
112 112 notified_events:
113 113 serialized: true
114 114 default:
115 115 - issue_added
116 116 - issue_updated
117 117 mail_handler_api_enabled:
118 118 default: 0
119 119 mail_handler_api_key:
120 120 default:
121 121 issue_list_default_columns:
122 122 serialized: true
123 123 default:
124 124 - tracker
125 125 - status
126 126 - priority
127 127 - subject
128 128 - assigned_to
129 129 - updated_on
130 130 display_subprojects_issues:
131 131 default: 1
132 issue_done_ratio:
133 default: 'issue_field'
132 134 default_projects_public:
133 135 default: 1
134 136 default_projects_modules:
135 137 serialized: true
136 138 default:
137 139 - issue_tracking
138 140 - time_tracking
139 141 - news
140 142 - documents
141 143 - files
142 144 - wiki
143 145 - repository
144 146 - boards
145 147 # Role given to a non-admin user who creates a project
146 148 new_project_user_role_id:
147 149 format: int
148 150 default: ''
149 151 sequential_project_identifiers:
150 152 default: 0
151 153 # encodings used to convert repository files content to UTF-8
152 154 # multiple values accepted, comma separated
153 155 repositories_encodings:
154 156 default: ''
155 157 # encoding used to convert commit logs to UTF-8
156 158 commit_logs_encoding:
157 159 default: 'UTF-8'
158 160 repository_log_display_limit:
159 161 format: int
160 162 default: 100
161 163 ui_theme:
162 164 default: ''
163 165 emails_footer:
164 166 default: |-
165 167 You have received this notification because you have either subscribed to it, or are involved in it.
166 168 To change your notification preferences, please click here: http://hostname/my/account
167 169 gravatar_enabled:
168 170 default: 0
169 171 openid:
170 172 default: 0
171 173 gravatar_default:
172 174 default: ''
@@ -1,815 +1,816
1 1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2 2
3 3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 4 h1 {margin:0; padding:0; font-size: 24px;}
5 5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 7 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8 8
9 9 /***** Layout *****/
10 10 #wrapper {background: white;}
11 11
12 12 #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
13 13 #top-menu ul {margin: 0; padding: 0;}
14 14 #top-menu li {
15 15 float:left;
16 16 list-style-type:none;
17 17 margin: 0px 0px 0px 0px;
18 18 padding: 0px 0px 0px 0px;
19 19 white-space:nowrap;
20 20 }
21 21 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
22 22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
23 23
24 24 #account {float:right;}
25 25
26 26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
27 27 #header a {color:#f8f8f8;}
28 28 #header h1 a.ancestor { font-size: 80%; }
29 29 #quick-search {float:right;}
30 30
31 31 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
32 32 #main-menu ul {margin: 0; padding: 0;}
33 33 #main-menu li {
34 34 float:left;
35 35 list-style-type:none;
36 36 margin: 0px 2px 0px 0px;
37 37 padding: 0px 0px 0px 0px;
38 38 white-space:nowrap;
39 39 }
40 40 #main-menu li a {
41 41 display: block;
42 42 color: #fff;
43 43 text-decoration: none;
44 44 font-weight: bold;
45 45 margin: 0;
46 46 padding: 4px 10px 4px 10px;
47 47 }
48 48 #main-menu li a:hover {background:#759FCF; color:#fff;}
49 49 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
50 50
51 51 #main {background-color:#EEEEEE;}
52 52
53 53 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
54 54 * html #sidebar{ width: 17%; }
55 55 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
56 56 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
57 57 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
58 58
59 59 #content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
60 60 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
61 61 html>body #content { min-height: 600px; }
62 62 * html body #content { height: 600px; } /* IE */
63 63
64 64 #main.nosidebar #sidebar{ display: none; }
65 65 #main.nosidebar #content{ width: auto; border-right: 0; }
66 66
67 67 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
68 68
69 69 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
70 70 #login-form table td {padding: 6px;}
71 71 #login-form label {font-weight: bold;}
72 72 #login-form input#username, #login-form input#password { width: 300px; }
73 73
74 74 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
75 75
76 76 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
77 77
78 78 /***** Links *****/
79 79 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
80 80 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
81 81 a img{ border: 0; }
82 82
83 83 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
84 84
85 85 /***** Tables *****/
86 86 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
87 87 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
88 88 table.list td { vertical-align: top; }
89 89 table.list td.id { width: 2%; text-align: center;}
90 90 table.list td.checkbox { width: 15px; padding: 0px;}
91 91 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
92 92 table.list td.buttons a { padding-right: 0.6em; }
93 93
94 94 tr.project td.name a { padding-left: 16px; white-space:nowrap; }
95 95 tr.project.parent td.name a { background: url('../images/bullet_toggle_minus.png') no-repeat; }
96 96
97 97 tr.issue { text-align: center; white-space: nowrap; }
98 98 tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; }
99 99 tr.issue td.subject { text-align: left; }
100 100 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
101 101
102 102 tr.entry { border: 1px solid #f8f8f8; }
103 103 tr.entry td { white-space: nowrap; }
104 104 tr.entry td.filename { width: 30%; }
105 105 tr.entry td.size { text-align: right; font-size: 90%; }
106 106 tr.entry td.revision, tr.entry td.author { text-align: center; }
107 107 tr.entry td.age { text-align: right; }
108 108 tr.entry.file td.filename a { margin-left: 16px; }
109 109
110 110 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
111 111 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
112 112
113 113 tr.changeset td.author { text-align: center; width: 15%; }
114 114 tr.changeset td.committed_on { text-align: center; width: 15%; }
115 115
116 116 table.files tr.file td { text-align: center; }
117 117 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
118 118 table.files tr.file td.digest { font-size: 80%; }
119 119
120 120 table.members td.roles, table.memberships td.roles { width: 45%; }
121 121
122 122 tr.message { height: 2.6em; }
123 123 tr.message td.last_message { font-size: 80%; }
124 124 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
125 125 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
126 126
127 127 tr.version.closed, tr.version.closed a { color: #999; }
128 128 tr.version td.name { padding-left: 20px; }
129 129 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
130 130 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; }
131 131
132 132 tr.user td { width:13%; }
133 133 tr.user td.email { width:18%; }
134 134 tr.user td { white-space: nowrap; }
135 135 tr.user.locked, tr.user.registered { color: #aaa; }
136 136 tr.user.locked a, tr.user.registered a { color: #aaa; }
137 137
138 138 tr.time-entry { text-align: center; white-space: nowrap; }
139 139 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
140 140 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
141 141 td.hours .hours-dec { font-size: 0.9em; }
142 142
143 143 table.plugins td { vertical-align: middle; }
144 144 table.plugins td.configure { text-align: right; padding-right: 1em; }
145 145 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
146 146 table.plugins span.description { display: block; font-size: 0.9em; }
147 147 table.plugins span.url { display: block; font-size: 0.9em; }
148 148
149 149 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
150 150 table.list tbody tr.group span.count { color: #aaa; font-size: 80%; }
151 151
152 152 table.list tbody tr:hover { background-color:#ffffdd; }
153 153 table.list tbody tr.group:hover { background-color:inherit; }
154 154 table td {padding:2px;}
155 155 table p {margin:0;}
156 156 .odd {background-color:#f6f7f8;}
157 157 .even {background-color: #fff;}
158 158
159 159 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
160 160 a.sort.asc { background-image: url(../images/sort_asc.png); }
161 161 a.sort.desc { background-image: url(../images/sort_desc.png); }
162 162
163 163 table.attributes { width: 100% }
164 164 table.attributes th { vertical-align: top; text-align: left; }
165 165 table.attributes td { vertical-align: top; }
166 166
167 167 td.center {text-align:center;}
168 168
169 169 .highlight { background-color: #FCFD8D;}
170 170 .highlight.token-1 { background-color: #faa;}
171 171 .highlight.token-2 { background-color: #afa;}
172 172 .highlight.token-3 { background-color: #aaf;}
173 173
174 174 .box{
175 175 padding:6px;
176 176 margin-bottom: 10px;
177 177 background-color:#f6f6f6;
178 178 color:#505050;
179 179 line-height:1.5em;
180 180 border: 1px solid #e4e4e4;
181 181 }
182 182
183 183 div.square {
184 184 border: 1px solid #999;
185 185 float: left;
186 186 margin: .3em .4em 0 .4em;
187 187 overflow: hidden;
188 188 width: .6em; height: .6em;
189 189 }
190 190 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
191 191 .contextual input, .contextual select {font-size:0.9em;}
192 192 .message .contextual { margin-top: 0; }
193 193
194 194 .splitcontentleft{float:left; width:49%;}
195 195 .splitcontentright{float:right; width:49%;}
196 196 form {display: inline;}
197 197 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
198 198 fieldset {border: 1px solid #e4e4e4; margin:0;}
199 199 legend {color: #484848;}
200 200 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
201 201 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
202 202 blockquote blockquote { margin-left: 0;}
203 203 acronym { border-bottom: 1px dotted; cursor: help; }
204 204 textarea.wiki-edit { width: 99%; }
205 205 li p {margin-top: 0;}
206 206 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
207 207 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
208 208 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
209 209 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
210 210
211 211 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
212 212 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
213 213 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
214 214
215 215 fieldset#date-range p { margin: 2px 0 2px 0; }
216 216 fieldset#filters table { border-collapse: collapse; }
217 217 fieldset#filters table td { padding: 0; vertical-align: middle; }
218 218 fieldset#filters tr.filter { height: 2em; }
219 219 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
220 220 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
221 221
222 222 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
223 223 div#issue-changesets .changeset { padding: 4px;}
224 224 div#issue-changesets .changeset { border-bottom: 1px solid #ddd; }
225 225 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
226 226
227 227 div#activity dl, #search-results { margin-left: 2em; }
228 228 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
229 229 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
230 230 div#activity dt.me .time { border-bottom: 1px solid #999; }
231 231 div#activity dt .time { color: #777; font-size: 80%; }
232 232 div#activity dd .description, #search-results dd .description { font-style: italic; }
233 233 div#activity span.project:after, #search-results span.project:after { content: " -"; }
234 234 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
235 235
236 236 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
237 237
238 238 div#search-results-counts {float:right;}
239 239 div#search-results-counts ul { margin-top: 0.5em; }
240 240 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
241 241
242 242 dt.issue { background-image: url(../images/ticket.png); }
243 243 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
244 244 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
245 245 dt.issue-note { background-image: url(../images/ticket_note.png); }
246 246 dt.changeset { background-image: url(../images/changeset.png); }
247 247 dt.news { background-image: url(../images/news.png); }
248 248 dt.message { background-image: url(../images/message.png); }
249 249 dt.reply { background-image: url(../images/comments.png); }
250 250 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
251 251 dt.attachment { background-image: url(../images/attachment.png); }
252 252 dt.document { background-image: url(../images/document.png); }
253 253 dt.project { background-image: url(../images/projects.png); }
254 254 dt.time-entry { background-image: url(../images/time.png); }
255 255
256 256 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
257 257
258 258 div#roadmap fieldset.related-issues { margin-bottom: 1em; }
259 259 div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
260 260 div#roadmap .wiki h1:first-child { display: none; }
261 261 div#roadmap .wiki h1 { font-size: 120%; }
262 262 div#roadmap .wiki h2 { font-size: 110%; }
263 263
264 264 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
265 265 div#version-summary fieldset { margin-bottom: 1em; }
266 266 div#version-summary .total-hours { text-align: right; }
267 267
268 268 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
269 269 table#time-report tbody tr { font-style: italic; color: #777; }
270 270 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
271 271 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
272 272 table#time-report .hours-dec { font-size: 0.9em; }
273 273
274 274 form#issue-form .attributes { margin-bottom: 8px; }
275 275 form#issue-form .attributes p { padding-top: 1px; padding-bottom: 2px; }
276 276 form#issue-form .attributes select { min-width: 30%; }
277 277
278 278 ul.projects { margin: 0; padding-left: 1em; }
279 279 ul.projects.root { margin: 0; padding: 0; }
280 280 ul.projects ul { border-left: 3px solid #e0e0e0; }
281 281 ul.projects li { list-style-type:none; }
282 282 ul.projects li.root { margin-bottom: 1em; }
283 283 ul.projects li.child { margin-top: 1em;}
284 284 ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
285 285 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
286 286
287 287 #tracker_project_ids ul { margin: 0; padding-left: 1em; }
288 288 #tracker_project_ids li { list-style-type:none; }
289 289
290 290 ul.properties {padding:0; font-size: 0.9em; color: #777;}
291 291 ul.properties li {list-style-type:none;}
292 292 ul.properties li span {font-style:italic;}
293 293
294 294 .total-hours { font-size: 110%; font-weight: bold; }
295 295 .total-hours span.hours-int { font-size: 120%; }
296 296
297 297 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
298 298 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
299 299
300 300 .pagination {font-size: 90%}
301 301 p.pagination {margin-top:8px;}
302 302
303 303 /***** Tabular forms ******/
304 304 .tabular p{
305 305 margin: 0;
306 306 padding: 5px 0 8px 0;
307 307 padding-left: 180px; /*width of left column containing the label elements*/
308 308 height: 1%;
309 309 clear:left;
310 310 }
311 311
312 312 html>body .tabular p {overflow:hidden;}
313 313
314 314 .tabular label{
315 315 font-weight: bold;
316 316 float: left;
317 317 text-align: right;
318 318 margin-left: -180px; /*width of left column*/
319 319 width: 175px; /*width of labels. Should be smaller than left column to create some right
320 320 margin*/
321 321 }
322 322
323 323 .tabular label.floating{
324 324 font-weight: normal;
325 325 margin-left: 0px;
326 326 text-align: left;
327 327 width: 270px;
328 328 }
329 329
330 330 .tabular label.block{
331 331 font-weight: normal;
332 332 margin-left: 0px !important;
333 333 text-align: left;
334 334 float: none;
335 335 display: block;
336 336 width: auto;
337 337 }
338 338
339 339 input#time_entry_comments { width: 90%;}
340 340
341 341 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
342 342
343 343 .tabular.settings p{ padding-left: 300px; }
344 344 .tabular.settings label{ margin-left: -300px; width: 295px; }
345 345
346 346 .required {color: #bb0000;}
347 347 .summary {font-style: italic;}
348 348
349 349 #attachments_fields input[type=text] {margin-left: 8px; }
350 350
351 351 div.attachments { margin-top: 12px; }
352 352 div.attachments p { margin:4px 0 2px 0; }
353 353 div.attachments img { vertical-align: middle; }
354 354 div.attachments span.author { font-size: 0.9em; color: #888; }
355 355
356 356 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
357 357 .other-formats span + span:before { content: "| "; }
358 358
359 359 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
360 360
361 361 /* Project members tab */
362 362 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
363 363 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
364 364 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
365 365 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
366 366 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
367 367 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
368 368
369 369 table.members td.group { padding-left: 20px; background: url(../images/users.png) no-repeat 0% 0%; }
370 370
371 371 * html div#tab-content-members fieldset div { height: 450px; }
372 372
373 373 /***** Flash & error messages ****/
374 374 #errorExplanation, div.flash, .nodata, .warning {
375 375 padding: 4px 4px 4px 30px;
376 376 margin-bottom: 12px;
377 377 font-size: 1.1em;
378 378 border: 2px solid;
379 379 }
380 380
381 381 div.flash {margin-top: 8px;}
382 382
383 383 div.flash.error, #errorExplanation {
384 384 background: url(../images/false.png) 8px 5px no-repeat;
385 385 background-color: #ffe3e3;
386 386 border-color: #dd0000;
387 387 color: #550000;
388 388 }
389 389
390 390 div.flash.notice {
391 391 background: url(../images/true.png) 8px 5px no-repeat;
392 392 background-color: #dfffdf;
393 393 border-color: #9fcf9f;
394 394 color: #005f00;
395 395 }
396 396
397 397 div.flash.warning {
398 398 background: url(../images/warning.png) 8px 5px no-repeat;
399 399 background-color: #FFEBC1;
400 400 border-color: #FDBF3B;
401 401 color: #A6750C;
402 402 text-align: left;
403 403 }
404 404
405 405 .nodata, .warning {
406 406 text-align: center;
407 407 background-color: #FFEBC1;
408 408 border-color: #FDBF3B;
409 409 color: #A6750C;
410 410 }
411 411
412 412 #errorExplanation ul { font-size: 0.9em;}
413 413 #errorExplanation h2, #errorExplanation p { display: none; }
414 414
415 415 /***** Ajax indicator ******/
416 416 #ajax-indicator {
417 417 position: absolute; /* fixed not supported by IE */
418 418 background-color:#eee;
419 419 border: 1px solid #bbb;
420 420 top:35%;
421 421 left:40%;
422 422 width:20%;
423 423 font-weight:bold;
424 424 text-align:center;
425 425 padding:0.6em;
426 426 z-index:100;
427 427 filter:alpha(opacity=50);
428 428 opacity: 0.5;
429 429 }
430 430
431 431 html>body #ajax-indicator { position: fixed; }
432 432
433 433 #ajax-indicator span {
434 434 background-position: 0% 40%;
435 435 background-repeat: no-repeat;
436 436 background-image: url(../images/loading.gif);
437 437 padding-left: 26px;
438 438 vertical-align: bottom;
439 439 }
440 440
441 441 /***** Calendar *****/
442 442 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
443 443 table.cal thead th {width: 14%;}
444 444 table.cal tbody tr {height: 100px;}
445 445 table.cal th { background-color:#EEEEEE; padding: 4px; }
446 446 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
447 447 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
448 448 table.cal td.odd p.day-num {color: #bbb;}
449 449 table.cal td.today {background:#ffffdd;}
450 450 table.cal td.today p.day-num {font-weight: bold;}
451 451
452 452 /***** Tooltips ******/
453 453 .tooltip{position:relative;z-index:24;}
454 454 .tooltip:hover{z-index:25;color:#000;}
455 455 .tooltip span.tip{display: none; text-align:left;}
456 456
457 457 div.tooltip:hover span.tip{
458 458 display:block;
459 459 position:absolute;
460 460 top:12px; left:24px; width:270px;
461 461 border:1px solid #555;
462 462 background-color:#fff;
463 463 padding: 4px;
464 464 font-size: 0.8em;
465 465 color:#505050;
466 466 }
467 467
468 468 /***** Progress bar *****/
469 469 table.progress {
470 470 border: 1px solid #D7D7D7;
471 471 border-collapse: collapse;
472 472 border-spacing: 0pt;
473 473 empty-cells: show;
474 474 text-align: center;
475 475 float:left;
476 476 margin: 1px 6px 1px 0px;
477 477 }
478 478
479 479 table.progress td { height: 0.9em; }
480 480 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
481 481 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
482 482 table.progress td.open { background: #FFF none repeat scroll 0%; }
483 483 p.pourcent {font-size: 80%;}
484 484 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
485 485
486 486 /***** Tabs *****/
487 487 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
488 488 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
489 489 #content .tabs>ul { bottom:-1px; } /* others */
490 490 #content .tabs ul li {
491 491 float:left;
492 492 list-style-type:none;
493 493 white-space:nowrap;
494 494 margin-right:8px;
495 495 background:#fff;
496 496 }
497 497 #content .tabs ul li a{
498 498 display:block;
499 499 font-size: 0.9em;
500 500 text-decoration:none;
501 501 line-height:1.3em;
502 502 padding:4px 6px 4px 6px;
503 503 border: 1px solid #ccc;
504 504 border-bottom: 1px solid #bbbbbb;
505 505 background-color: #eeeeee;
506 506 color:#777;
507 507 font-weight:bold;
508 508 }
509 509
510 510 #content .tabs ul li a:hover {
511 511 background-color: #ffffdd;
512 512 text-decoration:none;
513 513 }
514 514
515 515 #content .tabs ul li a.selected {
516 516 background-color: #fff;
517 517 border: 1px solid #bbbbbb;
518 518 border-bottom: 1px solid #fff;
519 519 }
520 520
521 521 #content .tabs ul li a.selected:hover {
522 522 background-color: #fff;
523 523 }
524 524
525 525 /***** Auto-complete *****/
526 526 div.autocomplete {
527 527 position:absolute;
528 528 width:250px;
529 529 background-color:white;
530 530 margin:0;
531 531 padding:0;
532 532 }
533 533 div.autocomplete ul {
534 534 list-style-type:none;
535 535 margin:0;
536 536 padding:0;
537 537 }
538 538 div.autocomplete ul li.selected { background-color: #ffb;}
539 539 div.autocomplete ul li {
540 540 list-style-type:none;
541 541 display:block;
542 542 margin:0;
543 543 padding:2px;
544 544 cursor:pointer;
545 545 font-size: 90%;
546 546 border-bottom: 1px solid #ccc;
547 547 border-left: 1px solid #ccc;
548 548 border-right: 1px solid #ccc;
549 549 }
550 550 div.autocomplete ul li span.informal {
551 551 font-size: 80%;
552 552 color: #aaa;
553 553 }
554 554
555 555 /***** Diff *****/
556 556 .diff_out { background: #fcc; }
557 557 .diff_in { background: #cfc; }
558 558
559 559 /***** Wiki *****/
560 560 div.wiki table {
561 561 border: 1px solid #505050;
562 562 border-collapse: collapse;
563 563 margin-bottom: 1em;
564 564 }
565 565
566 566 div.wiki table, div.wiki td, div.wiki th {
567 567 border: 1px solid #bbb;
568 568 padding: 4px;
569 569 }
570 570
571 571 div.wiki .external {
572 572 background-position: 0% 60%;
573 573 background-repeat: no-repeat;
574 574 padding-left: 12px;
575 575 background-image: url(../images/external.png);
576 576 }
577 577
578 578 div.wiki a.new {
579 579 color: #b73535;
580 580 }
581 581
582 582 div.wiki pre {
583 583 margin: 1em 1em 1em 1.6em;
584 584 padding: 2px;
585 585 background-color: #fafafa;
586 586 border: 1px solid #dadada;
587 587 width:95%;
588 588 overflow-x: auto;
589 589 }
590 590
591 591 div.wiki ul.toc {
592 592 background-color: #ffffdd;
593 593 border: 1px solid #e4e4e4;
594 594 padding: 4px;
595 595 line-height: 1.2em;
596 596 margin-bottom: 12px;
597 597 margin-right: 12px;
598 598 margin-left: 0;
599 599 display: table
600 600 }
601 601 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
602 602
603 603 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
604 604 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
605 605 div.wiki ul.toc li { list-style-type:none;}
606 606 div.wiki ul.toc li.heading2 { margin-left: 6px; }
607 607 div.wiki ul.toc li.heading3 { margin-left: 12px; font-size: 0.8em; }
608 608
609 609 div.wiki ul.toc a {
610 610 font-size: 0.9em;
611 611 font-weight: normal;
612 612 text-decoration: none;
613 613 color: #606060;
614 614 }
615 615 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
616 616
617 617 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
618 618 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
619 619 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
620 620
621 621 /***** My page layout *****/
622 622 .block-receiver {
623 623 border:1px dashed #c0c0c0;
624 624 margin-bottom: 20px;
625 625 padding: 15px 0 15px 0;
626 626 }
627 627
628 628 .mypage-box {
629 629 margin:0 0 20px 0;
630 630 color:#505050;
631 631 line-height:1.5em;
632 632 }
633 633
634 634 .handle {
635 635 cursor: move;
636 636 }
637 637
638 638 a.close-icon {
639 639 display:block;
640 640 margin-top:3px;
641 641 overflow:hidden;
642 642 width:12px;
643 643 height:12px;
644 644 background-repeat: no-repeat;
645 645 cursor:pointer;
646 646 background-image:url('../images/close.png');
647 647 }
648 648
649 649 a.close-icon:hover {
650 650 background-image:url('../images/close_hl.png');
651 651 }
652 652
653 653 /***** Gantt chart *****/
654 654 .gantt_hdr {
655 655 position:absolute;
656 656 top:0;
657 657 height:16px;
658 658 border-top: 1px solid #c0c0c0;
659 659 border-bottom: 1px solid #c0c0c0;
660 660 border-right: 1px solid #c0c0c0;
661 661 text-align: center;
662 662 overflow: hidden;
663 663 }
664 664
665 665 .task {
666 666 position: absolute;
667 667 height:8px;
668 668 font-size:0.8em;
669 669 color:#888;
670 670 padding:0;
671 671 margin:0;
672 672 line-height:0.8em;
673 673 }
674 674
675 675 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
676 676 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
677 677 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
678 678 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
679 679
680 680 /***** Icons *****/
681 681 .icon {
682 682 background-position: 0% 40%;
683 683 background-repeat: no-repeat;
684 684 padding-left: 20px;
685 685 padding-top: 2px;
686 686 padding-bottom: 3px;
687 687 }
688 688
689 689 .icon22 {
690 690 background-position: 0% 40%;
691 691 background-repeat: no-repeat;
692 692 padding-left: 26px;
693 693 line-height: 22px;
694 694 vertical-align: middle;
695 695 }
696 696
697 697 .icon-add { background-image: url(../images/add.png); }
698 698 .icon-edit { background-image: url(../images/edit.png); }
699 699 .icon-copy { background-image: url(../images/copy.png); }
700 700 .icon-duplicate { background-image: url(../images/duplicate.png); }
701 701 .icon-del { background-image: url(../images/delete.png); }
702 702 .icon-move { background-image: url(../images/move.png); }
703 703 .icon-save { background-image: url(../images/save.png); }
704 704 .icon-cancel { background-image: url(../images/cancel.png); }
705 .icon-multiple { background-image: url(../images/table_multiple.png); }
705 706 .icon-folder { background-image: url(../images/folder.png); }
706 707 .open .icon-folder { background-image: url(../images/folder_open.png); }
707 708 .icon-package { background-image: url(../images/package.png); }
708 709 .icon-home { background-image: url(../images/home.png); }
709 710 .icon-user { background-image: url(../images/user.png); }
710 711 .icon-mypage { background-image: url(../images/user_page.png); }
711 712 .icon-admin { background-image: url(../images/admin.png); }
712 713 .icon-projects { background-image: url(../images/projects.png); }
713 714 .icon-help { background-image: url(../images/help.png); }
714 715 .icon-attachment { background-image: url(../images/attachment.png); }
715 716 .icon-index { background-image: url(../images/index.png); }
716 717 .icon-history { background-image: url(../images/history.png); }
717 718 .icon-time { background-image: url(../images/time.png); }
718 719 .icon-time-add { background-image: url(../images/time_add.png); }
719 720 .icon-stats { background-image: url(../images/stats.png); }
720 721 .icon-warning { background-image: url(../images/warning.png); }
721 722 .icon-fav { background-image: url(../images/fav.png); }
722 723 .icon-fav-off { background-image: url(../images/fav_off.png); }
723 724 .icon-reload { background-image: url(../images/reload.png); }
724 725 .icon-lock { background-image: url(../images/locked.png); }
725 726 .icon-unlock { background-image: url(../images/unlock.png); }
726 727 .icon-checked { background-image: url(../images/true.png); }
727 728 .icon-details { background-image: url(../images/zoom_in.png); }
728 729 .icon-report { background-image: url(../images/report.png); }
729 730 .icon-comment { background-image: url(../images/comment.png); }
730 731
731 732 .icon-file { background-image: url(../images/files/default.png); }
732 733 .icon-file.text-plain { background-image: url(../images/files/text.png); }
733 734 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
734 735 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
735 736 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
736 737 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
737 738 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
738 739 .icon-file.image-gif { background-image: url(../images/files/image.png); }
739 740 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
740 741 .icon-file.image-png { background-image: url(../images/files/image.png); }
741 742 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
742 743 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
743 744 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
744 745 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
745 746
746 747 .icon22-projects { background-image: url(../images/22x22/projects.png); }
747 748 .icon22-users { background-image: url(../images/22x22/users.png); }
748 749 .icon22-groups { background-image: url(../images/22x22/groups.png); }
749 750 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
750 751 .icon22-role { background-image: url(../images/22x22/role.png); }
751 752 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
752 753 .icon22-options { background-image: url(../images/22x22/options.png); }
753 754 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
754 755 .icon22-authent { background-image: url(../images/22x22/authent.png); }
755 756 .icon22-info { background-image: url(../images/22x22/info.png); }
756 757 .icon22-comment { background-image: url(../images/22x22/comment.png); }
757 758 .icon22-package { background-image: url(../images/22x22/package.png); }
758 759 .icon22-settings { background-image: url(../images/22x22/settings.png); }
759 760 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
760 761
761 762 img.gravatar {
762 763 padding: 2px;
763 764 border: solid 1px #d5d5d5;
764 765 background: #fff;
765 766 }
766 767
767 768 div.issue img.gravatar {
768 769 float: right;
769 770 margin: 0 0 0 1em;
770 771 padding: 5px;
771 772 }
772 773
773 774 div.issue table img.gravatar {
774 775 height: 14px;
775 776 width: 14px;
776 777 padding: 2px;
777 778 float: left;
778 779 margin: 0 0.5em 0 0;
779 780 }
780 781
781 782 #history img.gravatar {
782 783 padding: 3px;
783 784 margin: 0 1.5em 1em 0;
784 785 float: left;
785 786 }
786 787
787 788 td.username img.gravatar {
788 789 float: left;
789 790 margin: 0 1em 0 0;
790 791 }
791 792
792 793 #activity dt img.gravatar {
793 794 float: left;
794 795 margin: 0 1em 1em 0;
795 796 }
796 797
797 798 #activity dt,
798 799 .journal {
799 800 clear: left;
800 801 }
801 802
802 803 .gravatar-margin {
803 804 margin-left: 40px;
804 805 }
805 806
806 807 h2 img { vertical-align:middle; }
807 808
808 809
809 810 /***** Media print specific styles *****/
810 811 @media print {
811 812 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
812 813 #main { background: #fff; }
813 814 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
814 815 #wiki_add_attachment { display:none; }
815 816 }
@@ -1,73 +1,96
1 1 require File.dirname(__FILE__) + '/../test_helper'
2 2 require 'issue_statuses_controller'
3 3
4 4 # Re-raise errors caught by the controller.
5 5 class IssueStatusesController; def rescue_action(e) raise e end; end
6 6
7 7
8 8 class IssueStatusesControllerTest < ActionController::TestCase
9 9 fixtures :issue_statuses, :issues
10 10
11 11 def setup
12 12 @controller = IssueStatusesController.new
13 13 @request = ActionController::TestRequest.new
14 14 @response = ActionController::TestResponse.new
15 15 User.current = nil
16 16 @request.session[:user_id] = 1 # admin
17 17 end
18 18
19 19 def test_index
20 20 # TODO: unify with #list
21 21 get :index
22 22 assert_response :success
23 23 assert_template 'list'
24 24 end
25 25
26 26 def test_new
27 27 get :new
28 28 assert_response :success
29 29 assert_template 'new'
30 30 end
31 31
32 32 def test_create
33 33 assert_difference 'IssueStatus.count' do
34 34 post :create, :issue_status => {:name => 'New status'}
35 35 end
36 36 assert_redirected_to 'issue_statuses/list'
37 37 status = IssueStatus.find(:first, :order => 'id DESC')
38 38 assert_equal 'New status', status.name
39 39 end
40 40
41 41 def test_edit
42 42 get :edit, :id => '3'
43 43 assert_response :success
44 44 assert_template 'edit'
45 45 end
46 46
47 47 def test_update
48 48 post :update, :id => '3', :issue_status => {:name => 'Renamed status'}
49 49 assert_redirected_to 'issue_statuses/list'
50 50 status = IssueStatus.find(3)
51 51 assert_equal 'Renamed status', status.name
52 52 end
53 53
54 54 def test_destroy
55 55 Issue.delete_all("status_id = 1")
56 56
57 57 assert_difference 'IssueStatus.count', -1 do
58 58 post :destroy, :id => '1'
59 59 end
60 60 assert_redirected_to 'issue_statuses/list'
61 61 assert_nil IssueStatus.find_by_id(1)
62 62 end
63 63
64 64 def test_destroy_should_block_if_status_in_use
65 65 assert_not_nil Issue.find_by_status_id(1)
66 66
67 67 assert_no_difference 'IssueStatus.count' do
68 68 post :destroy, :id => '1'
69 69 end
70 70 assert_redirected_to 'issue_statuses/list'
71 71 assert_not_nil IssueStatus.find_by_id(1)
72 72 end
73
74 context "on POST to :update_issue_done_ratio" do
75 context "with Setting.issue_done_ratio using the issue_field" do
76 setup do
77 Setting.issue_done_ratio = 'issue_field'
78 post :update_issue_done_ratio
79 end
80
81 should_set_the_flash_to /not updated/
82 should_redirect_to('the list') { '/issue_statuses/list' }
83 end
84
85 context "with Setting.issue_done_ratio using the issue_status" do
86 setup do
87 Setting.issue_done_ratio = 'issue_status'
88 post :update_issue_done_ratio
89 end
90
91 should_set_the_flash_to /Issue done ratios updated/
92 should_redirect_to('the list') { '/issue_statuses/list' }
93 end
94 end
95
73 96 end
@@ -1,69 +1,105
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19
20 20 class IssueStatusTest < ActiveSupport::TestCase
21 21 fixtures :issue_statuses, :issues
22 22
23 23 def test_create
24 24 status = IssueStatus.new :name => "Assigned"
25 25 assert !status.save
26 26 # status name uniqueness
27 27 assert_equal 1, status.errors.count
28 28
29 29 status.name = "Test Status"
30 30 assert status.save
31 31 assert !status.is_default
32 32 end
33 33
34 34 def test_destroy
35 35 count_before = IssueStatus.count
36 36 status = IssueStatus.find(3)
37 37 assert status.destroy
38 38 assert_equal count_before - 1, IssueStatus.count
39 39 end
40 40
41 41 def test_destroy_status_in_use
42 42 # Status assigned to an Issue
43 43 status = Issue.find(1).status
44 44 assert_raise(RuntimeError, "Can't delete status") { status.destroy }
45 45 end
46 46
47 47 def test_default
48 48 status = IssueStatus.default
49 49 assert_kind_of IssueStatus, status
50 50 end
51 51
52 52 def test_change_default
53 53 status = IssueStatus.find(2)
54 54 assert !status.is_default
55 55 status.is_default = true
56 56 assert status.save
57 57 status.reload
58 58
59 59 assert_equal status, IssueStatus.default
60 60 assert !IssueStatus.find(1).is_default
61 61 end
62 62
63 63 def test_reorder_should_not_clear_default_status
64 64 status = IssueStatus.default
65 65 status.move_to_bottom
66 66 status.reload
67 67 assert status.is_default?
68 68 end
69
70 context "#update_done_ratios" do
71 setup do
72 @issue = Issue.find(1)
73 @issue_status = IssueStatus.find(1)
74 @issue_status.update_attribute(:default_done_ratio, 50)
75 end
76
77 context "with Setting.issue_done_ratio using the issue_field" do
78 setup do
79 Setting.issue_done_ratio = 'issue_field'
80 end
81
82 should "change nothing" do
83 IssueStatus.update_issue_done_ratios
84
85 assert_equal 0, Issue.count(:conditions => {:done_ratio => 50})
86 end
87 end
88
89 context "with Setting.issue_done_ratio using the issue_status" do
90 setup do
91 Setting.issue_done_ratio = 'issue_status'
92 end
93
94 should "update all of the issue's done_ratios to match their Issue Status" do
95 IssueStatus.update_issue_done_ratios
96
97 issues = Issue.find([1,3,4,5,6,7,9,10])
98 issues.each do |issue|
99 assert_equal @issue_status, issue.status
100 assert_equal 50, issue.read_attribute(:done_ratio)
101 end
102 end
103 end
104 end
69 105 end
@@ -1,532 +1,592
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19
20 20 class IssueTest < ActiveSupport::TestCase
21 21 fixtures :projects, :users, :members, :member_roles, :roles,
22 22 :trackers, :projects_trackers,
23 23 :versions,
24 24 :issue_statuses, :issue_categories, :issue_relations, :workflows,
25 25 :enumerations,
26 26 :issues,
27 27 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
28 28 :time_entries
29 29
30 30 def test_create
31 31 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
32 32 assert issue.save
33 33 issue.reload
34 34 assert_equal 1.5, issue.estimated_hours
35 35 end
36 36
37 37 def test_create_minimal
38 38 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create')
39 39 assert issue.save
40 40 assert issue.description.nil?
41 41 end
42 42
43 43 def test_create_with_required_custom_field
44 44 field = IssueCustomField.find_by_name('Database')
45 45 field.update_attribute(:is_required, true)
46 46
47 47 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
48 48 assert issue.available_custom_fields.include?(field)
49 49 # No value for the custom field
50 50 assert !issue.save
51 51 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
52 52 # Blank value
53 53 issue.custom_field_values = { field.id => '' }
54 54 assert !issue.save
55 55 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
56 56 # Invalid value
57 57 issue.custom_field_values = { field.id => 'SQLServer' }
58 58 assert !issue.save
59 59 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
60 60 # Valid value
61 61 issue.custom_field_values = { field.id => 'PostgreSQL' }
62 62 assert issue.save
63 63 issue.reload
64 64 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
65 65 end
66 66
67 67 def test_visible_scope_for_anonymous
68 68 # Anonymous user should see issues of public projects only
69 69 issues = Issue.visible(User.anonymous).all
70 70 assert issues.any?
71 71 assert_nil issues.detect {|issue| !issue.project.is_public?}
72 72 # Anonymous user should not see issues without permission
73 73 Role.anonymous.remove_permission!(:view_issues)
74 74 issues = Issue.visible(User.anonymous).all
75 75 assert issues.empty?
76 76 end
77 77
78 78 def test_visible_scope_for_user
79 79 user = User.find(9)
80 80 assert user.projects.empty?
81 81 # Non member user should see issues of public projects only
82 82 issues = Issue.visible(user).all
83 83 assert issues.any?
84 84 assert_nil issues.detect {|issue| !issue.project.is_public?}
85 85 # Non member user should not see issues without permission
86 86 Role.non_member.remove_permission!(:view_issues)
87 87 user.reload
88 88 issues = Issue.visible(user).all
89 89 assert issues.empty?
90 90 # User should see issues of projects for which he has view_issues permissions only
91 91 Member.create!(:principal => user, :project_id => 2, :role_ids => [1])
92 92 user.reload
93 93 issues = Issue.visible(user).all
94 94 assert issues.any?
95 95 assert_nil issues.detect {|issue| issue.project_id != 2}
96 96 end
97 97
98 98 def test_visible_scope_for_admin
99 99 user = User.find(1)
100 100 user.members.each(&:destroy)
101 101 assert user.projects.empty?
102 102 issues = Issue.visible(user).all
103 103 assert issues.any?
104 104 # Admin should see issues on private projects that he does not belong to
105 105 assert issues.detect {|issue| !issue.project.is_public?}
106 106 end
107 107
108 108 def test_errors_full_messages_should_include_custom_fields_errors
109 109 field = IssueCustomField.find_by_name('Database')
110 110
111 111 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
112 112 assert issue.available_custom_fields.include?(field)
113 113 # Invalid value
114 114 issue.custom_field_values = { field.id => 'SQLServer' }
115 115
116 116 assert !issue.valid?
117 117 assert_equal 1, issue.errors.full_messages.size
118 118 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}", issue.errors.full_messages.first
119 119 end
120 120
121 121 def test_update_issue_with_required_custom_field
122 122 field = IssueCustomField.find_by_name('Database')
123 123 field.update_attribute(:is_required, true)
124 124
125 125 issue = Issue.find(1)
126 126 assert_nil issue.custom_value_for(field)
127 127 assert issue.available_custom_fields.include?(field)
128 128 # No change to custom values, issue can be saved
129 129 assert issue.save
130 130 # Blank value
131 131 issue.custom_field_values = { field.id => '' }
132 132 assert !issue.save
133 133 # Valid value
134 134 issue.custom_field_values = { field.id => 'PostgreSQL' }
135 135 assert issue.save
136 136 issue.reload
137 137 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
138 138 end
139 139
140 140 def test_should_not_update_attributes_if_custom_fields_validation_fails
141 141 issue = Issue.find(1)
142 142 field = IssueCustomField.find_by_name('Database')
143 143 assert issue.available_custom_fields.include?(field)
144 144
145 145 issue.custom_field_values = { field.id => 'Invalid' }
146 146 issue.subject = 'Should be not be saved'
147 147 assert !issue.save
148 148
149 149 issue.reload
150 150 assert_equal "Can't print recipes", issue.subject
151 151 end
152 152
153 153 def test_should_not_recreate_custom_values_objects_on_update
154 154 field = IssueCustomField.find_by_name('Database')
155 155
156 156 issue = Issue.find(1)
157 157 issue.custom_field_values = { field.id => 'PostgreSQL' }
158 158 assert issue.save
159 159 custom_value = issue.custom_value_for(field)
160 160 issue.reload
161 161 issue.custom_field_values = { field.id => 'MySQL' }
162 162 assert issue.save
163 163 issue.reload
164 164 assert_equal custom_value.id, issue.custom_value_for(field).id
165 165 end
166 166
167 167 def test_assigning_tracker_id_should_reload_custom_fields_values
168 168 issue = Issue.new(:project => Project.find(1))
169 169 assert issue.custom_field_values.empty?
170 170 issue.tracker_id = 1
171 171 assert issue.custom_field_values.any?
172 172 end
173 173
174 174 def test_assigning_attributes_should_assign_tracker_id_first
175 175 attributes = ActiveSupport::OrderedHash.new
176 176 attributes['custom_field_values'] = { '1' => 'MySQL' }
177 177 attributes['tracker_id'] = '1'
178 178 issue = Issue.new(:project => Project.find(1))
179 179 issue.attributes = attributes
180 180 assert_not_nil issue.custom_value_for(1)
181 181 assert_equal 'MySQL', issue.custom_value_for(1).value
182 182 end
183 183
184 184 def test_should_update_issue_with_disabled_tracker
185 185 p = Project.find(1)
186 186 issue = Issue.find(1)
187 187
188 188 p.trackers.delete(issue.tracker)
189 189 assert !p.trackers.include?(issue.tracker)
190 190
191 191 issue.reload
192 192 issue.subject = 'New subject'
193 193 assert issue.save
194 194 end
195 195
196 196 def test_should_not_set_a_disabled_tracker
197 197 p = Project.find(1)
198 198 p.trackers.delete(Tracker.find(2))
199 199
200 200 issue = Issue.find(1)
201 201 issue.tracker_id = 2
202 202 issue.subject = 'New subject'
203 203 assert !issue.save
204 204 assert_not_nil issue.errors.on(:tracker_id)
205 205 end
206 206
207 207 def test_category_based_assignment
208 208 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
209 209 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
210 210 end
211 211
212 212 def test_copy
213 213 issue = Issue.new.copy_from(1)
214 214 assert issue.save
215 215 issue.reload
216 216 orig = Issue.find(1)
217 217 assert_equal orig.subject, issue.subject
218 218 assert_equal orig.tracker, issue.tracker
219 219 assert_equal orig.custom_values.first.value, issue.custom_values.first.value
220 220 end
221 221
222 222 def test_copy_should_copy_status
223 223 orig = Issue.find(8)
224 224 assert orig.status != IssueStatus.default
225 225
226 226 issue = Issue.new.copy_from(orig)
227 227 assert issue.save
228 228 issue.reload
229 229 assert_equal orig.status, issue.status
230 230 end
231 231
232 232 def test_should_close_duplicates
233 233 # Create 3 issues
234 234 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
235 235 assert issue1.save
236 236 issue2 = issue1.clone
237 237 assert issue2.save
238 238 issue3 = issue1.clone
239 239 assert issue3.save
240 240
241 241 # 2 is a dupe of 1
242 242 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
243 243 # And 3 is a dupe of 2
244 244 IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
245 245 # And 3 is a dupe of 1 (circular duplicates)
246 246 IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
247 247
248 248 assert issue1.reload.duplicates.include?(issue2)
249 249
250 250 # Closing issue 1
251 251 issue1.init_journal(User.find(:first), "Closing issue1")
252 252 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
253 253 assert issue1.save
254 254 # 2 and 3 should be also closed
255 255 assert issue2.reload.closed?
256 256 assert issue3.reload.closed?
257 257 end
258 258
259 259 def test_should_not_close_duplicated_issue
260 260 # Create 3 issues
261 261 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
262 262 assert issue1.save
263 263 issue2 = issue1.clone
264 264 assert issue2.save
265 265
266 266 # 2 is a dupe of 1
267 267 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
268 268 # 2 is a dup of 1 but 1 is not a duplicate of 2
269 269 assert !issue2.reload.duplicates.include?(issue1)
270 270
271 271 # Closing issue 2
272 272 issue2.init_journal(User.find(:first), "Closing issue2")
273 273 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
274 274 assert issue2.save
275 275 # 1 should not be also closed
276 276 assert !issue1.reload.closed?
277 277 end
278 278
279 279 def test_assignable_versions
280 280 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
281 281 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
282 282 end
283 283
284 284 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
285 285 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
286 286 assert !issue.save
287 287 assert_not_nil issue.errors.on(:fixed_version_id)
288 288 end
289 289
290 290 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
291 291 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
292 292 assert !issue.save
293 293 assert_not_nil issue.errors.on(:fixed_version_id)
294 294 end
295 295
296 296 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
297 297 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
298 298 assert issue.save
299 299 end
300 300
301 301 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
302 302 issue = Issue.find(11)
303 303 assert_equal 'closed', issue.fixed_version.status
304 304 issue.subject = 'Subject changed'
305 305 assert issue.save
306 306 end
307 307
308 308 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
309 309 issue = Issue.find(11)
310 310 issue.status_id = 1
311 311 assert !issue.save
312 312 assert_not_nil issue.errors.on_base
313 313 end
314 314
315 315 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
316 316 issue = Issue.find(11)
317 317 issue.status_id = 1
318 318 issue.fixed_version_id = 3
319 319 assert issue.save
320 320 end
321 321
322 322 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
323 323 issue = Issue.find(12)
324 324 assert_equal 'locked', issue.fixed_version.status
325 325 issue.status_id = 1
326 326 assert issue.save
327 327 end
328 328
329 329 def test_move_to_another_project_with_same_category
330 330 issue = Issue.find(1)
331 331 assert issue.move_to(Project.find(2))
332 332 issue.reload
333 333 assert_equal 2, issue.project_id
334 334 # Category changes
335 335 assert_equal 4, issue.category_id
336 336 # Make sure time entries were move to the target project
337 337 assert_equal 2, issue.time_entries.first.project_id
338 338 end
339 339
340 340 def test_move_to_another_project_without_same_category
341 341 issue = Issue.find(2)
342 342 assert issue.move_to(Project.find(2))
343 343 issue.reload
344 344 assert_equal 2, issue.project_id
345 345 # Category cleared
346 346 assert_nil issue.category_id
347 347 end
348 348
349 349 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
350 350 issue = Issue.find(1)
351 351 issue.update_attribute(:fixed_version_id, 1)
352 352 assert issue.move_to(Project.find(2))
353 353 issue.reload
354 354 assert_equal 2, issue.project_id
355 355 # Cleared fixed_version
356 356 assert_equal nil, issue.fixed_version
357 357 end
358 358
359 359 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
360 360 issue = Issue.find(1)
361 361 issue.update_attribute(:fixed_version_id, 4)
362 362 assert issue.move_to(Project.find(5))
363 363 issue.reload
364 364 assert_equal 5, issue.project_id
365 365 # Keep fixed_version
366 366 assert_equal 4, issue.fixed_version_id
367 367 end
368 368
369 369 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
370 370 issue = Issue.find(1)
371 371 issue.update_attribute(:fixed_version_id, 1)
372 372 assert issue.move_to(Project.find(5))
373 373 issue.reload
374 374 assert_equal 5, issue.project_id
375 375 # Cleared fixed_version
376 376 assert_equal nil, issue.fixed_version
377 377 end
378 378
379 379 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
380 380 issue = Issue.find(1)
381 381 issue.update_attribute(:fixed_version_id, 7)
382 382 assert issue.move_to(Project.find(2))
383 383 issue.reload
384 384 assert_equal 2, issue.project_id
385 385 # Keep fixed_version
386 386 assert_equal 7, issue.fixed_version_id
387 387 end
388 388
389 389 def test_copy_to_the_same_project
390 390 issue = Issue.find(1)
391 391 copy = nil
392 392 assert_difference 'Issue.count' do
393 393 copy = issue.move_to(issue.project, nil, :copy => true)
394 394 end
395 395 assert_kind_of Issue, copy
396 396 assert_equal issue.project, copy.project
397 397 assert_equal "125", copy.custom_value_for(2).value
398 398 end
399 399
400 400 def test_copy_to_another_project_and_tracker
401 401 issue = Issue.find(1)
402 402 copy = nil
403 403 assert_difference 'Issue.count' do
404 404 copy = issue.move_to(Project.find(3), Tracker.find(2), :copy => true)
405 405 end
406 406 assert_kind_of Issue, copy
407 407 assert_equal Project.find(3), copy.project
408 408 assert_equal Tracker.find(2), copy.tracker
409 409 # Custom field #2 is not associated with target tracker
410 410 assert_nil copy.custom_value_for(2)
411 411 end
412 412
413 413 context "#move_to" do
414 414 context "as a copy" do
415 415 setup do
416 416 @issue = Issue.find(1)
417 417 @copy = nil
418 418 end
419 419
420 420 should "allow assigned_to changes" do
421 421 @copy = @issue.move_to(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}})
422 422 assert_equal 3, @copy.assigned_to_id
423 423 end
424 424
425 425 should "allow status changes" do
426 426 @copy = @issue.move_to(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:status_id => 2}})
427 427 assert_equal 2, @copy.status_id
428 428 end
429 429
430 430 should "allow start date changes" do
431 431 date = Date.today
432 432 @copy = @issue.move_to(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}})
433 433 assert_equal date, @copy.start_date
434 434 end
435 435
436 436 should "allow due date changes" do
437 437 date = Date.today
438 438 @copy = @issue.move_to(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:due_date => date}})
439 439
440 440 assert_equal date, @copy.due_date
441 441 end
442 442 end
443 443 end
444 444
445 445 def test_recipients_should_not_include_users_that_cannot_view_the_issue
446 446 issue = Issue.find(12)
447 447 assert issue.recipients.include?(issue.author.mail)
448 448 # move the issue to a private project
449 449 copy = issue.move_to(Project.find(5), Tracker.find(2), :copy => true)
450 450 # author is not a member of project anymore
451 451 assert !copy.recipients.include?(copy.author.mail)
452 452 end
453 453
454 454 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
455 455 user = User.find(3)
456 456 issue = Issue.find(9)
457 457 Watcher.create!(:user => user, :watchable => issue)
458 458 assert issue.watched_by?(user)
459 459 assert !issue.watcher_recipients.include?(user.mail)
460 460 end
461 461
462 462 def test_issue_destroy
463 463 Issue.find(1).destroy
464 464 assert_nil Issue.find_by_id(1)
465 465 assert_nil TimeEntry.find_by_issue_id(1)
466 466 end
467 467
468 468 def test_blocked
469 469 blocked_issue = Issue.find(9)
470 470 blocking_issue = Issue.find(10)
471 471
472 472 assert blocked_issue.blocked?
473 473 assert !blocking_issue.blocked?
474 474 end
475 475
476 476 def test_blocked_issues_dont_allow_closed_statuses
477 477 blocked_issue = Issue.find(9)
478 478
479 479 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
480 480 assert !allowed_statuses.empty?
481 481 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
482 482 assert closed_statuses.empty?
483 483 end
484 484
485 485 def test_unblocked_issues_allow_closed_statuses
486 486 blocking_issue = Issue.find(10)
487 487
488 488 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
489 489 assert !allowed_statuses.empty?
490 490 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
491 491 assert !closed_statuses.empty?
492 492 end
493 493
494 494 def test_overdue
495 495 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
496 496 assert !Issue.new(:due_date => Date.today).overdue?
497 497 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
498 498 assert !Issue.new(:due_date => nil).overdue?
499 499 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
500 500 end
501 501
502 502 def test_assignable_users
503 503 assert_kind_of User, Issue.find(1).assignable_users.first
504 504 end
505 505
506 506 def test_create_should_send_email_notification
507 507 ActionMailer::Base.deliveries.clear
508 508 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :estimated_hours => '1:30')
509 509
510 510 assert issue.save
511 511 assert_equal 1, ActionMailer::Base.deliveries.size
512 512 end
513 513
514 514 def test_stale_issue_should_not_send_email_notification
515 515 ActionMailer::Base.deliveries.clear
516 516 issue = Issue.find(1)
517 517 stale = Issue.find(1)
518 518
519 519 issue.init_journal(User.find(1))
520 520 issue.subject = 'Subjet update'
521 521 assert issue.save
522 522 assert_equal 1, ActionMailer::Base.deliveries.size
523 523 ActionMailer::Base.deliveries.clear
524 524
525 525 stale.init_journal(User.find(1))
526 526 stale.subject = 'Another subjet update'
527 527 assert_raise ActiveRecord::StaleObjectError do
528 528 stale.save
529 529 end
530 530 assert ActionMailer::Base.deliveries.empty?
531 531 end
532
533 context "#done_ratio" do
534 setup do
535 @issue = Issue.find(1)
536 @issue_status = IssueStatus.find(1)
537 @issue_status.update_attribute(:default_done_ratio, 50)
538 end
539
540 context "with Setting.issue_done_ratio using the issue_field" do
541 setup do
542 Setting.issue_done_ratio = 'issue_field'
543 end
544
545 should "read the issue's field" do
546 assert_equal 0, @issue.done_ratio
547 end
548 end
549
550 context "with Setting.issue_done_ratio using the issue_status" do
551 setup do
552 Setting.issue_done_ratio = 'issue_status'
553 end
554
555 should "read the Issue Status's default done ratio" do
556 assert_equal 50, @issue.done_ratio
557 end
558 end
559 end
560
561 context "#update_done_ratio_from_issue_status" do
562 setup do
563 @issue = Issue.find(1)
564 @issue_status = IssueStatus.find(1)
565 @issue_status.update_attribute(:default_done_ratio, 50)
566 end
567
568 context "with Setting.issue_done_ratio using the issue_field" do
569 setup do
570 Setting.issue_done_ratio = 'issue_field'
571 end
572
573 should "not change the issue" do
574 @issue.update_done_ratio_from_issue_status
575
576 assert_equal 0, @issue.done_ratio
577 end
578 end
579
580 context "with Setting.issue_done_ratio using the issue_status" do
581 setup do
582 Setting.issue_done_ratio = 'issue_status'
583 end
584
585 should "not change the issue's done ratio" do
586 @issue.update_done_ratio_from_issue_status
587
588 assert_equal 50, @issue.done_ratio
589 end
590 end
591 end
532 592 end
General Comments 0
You need to be logged in to leave comments. Login now