Patch #1650 » timelogging.patch
db/migrate/095_add_start_and_end_time_to_time_entries.rb (revision 0) | ||
---|---|---|
1 |
class AddStartAndEndTimeToTimeEntries < ActiveRecord::Migration |
|
2 |
def self.up |
|
3 |
add_column :time_entries, :start_time, :datetime |
|
4 |
add_column :time_entries, :end_time, :datetime |
|
5 |
end |
|
6 |
|
|
7 |
def self.down |
|
8 |
remove_column :time_entries, :start_time |
|
9 |
remove_column :time_entries, :end_time |
|
10 |
end |
|
11 |
end |
db/migrate/096_allow_null_time_entry_hours.rb (revision 0) | ||
---|---|---|
1 |
class AllowNullTimeEntryHours < ActiveRecord::Migration |
|
2 |
def self.up |
|
3 |
change_column :time_entries, :hours, :float, :null => true |
|
4 |
end |
|
5 |
|
|
6 |
def self.down |
|
7 |
# not needed |
|
8 |
end |
|
9 |
end |
app/views/issues/_edit.rhtml (working copy) | ||
---|---|---|
1 |
<% content_for :header_tags do %> |
|
2 |
<%= javascript_include_tag 'time' %> |
|
3 |
<% end %> |
|
1 | 4 |
<% labelled_tabular_form_for :issue, @issue, |
2 | 5 |
:url => {:action => 'edit', :id => @issue}, |
3 | 6 |
:html => {:id => 'issue-form', |
... | ... | |
18 | 21 |
<% if authorize_for('timelog', 'edit') %> |
19 | 22 |
<fieldset class="tabular"><legend><%= l(:button_log_time) %></legend> |
20 | 23 |
<% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %> |
24 |
<%= time_entry.hidden_field :id %> |
|
21 | 25 |
<div class="splitcontentleft"> |
22 | 26 |
<p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p> |
27 |
<p><%= time_entry.time_field :start_time %></p> |
|
23 | 28 |
</div> |
24 | 29 |
<div class="splitcontentright"> |
25 | 30 |
<p><%= time_entry.select :activity_id, activity_collection_for_select_options %></p> |
31 |
<p><%= time_entry.time_field :end_time %></p> |
|
26 | 32 |
</div> |
27 | 33 |
<p><%= time_entry.text_field :comments, :size => 60 %></p> |
28 | 34 |
<% end %> |
app/views/issues/show.rhtml (working copy) | ||
---|---|---|
33 | 33 |
<td><b><%=l(:field_category)%>:</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td> |
34 | 34 |
<% if User.current.allowed_to?(:view_time_entries, @project) %> |
35 | 35 |
<td><b><%=l(:label_spent_time)%>:</b></td> |
36 |
<td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
|
|
36 |
<td><%= spent_hours(@issue) %></td>
|
|
37 | 37 |
<% end %> |
38 | 38 |
</tr> |
39 | 39 |
<tr> |
app/views/timelog/_list.rhtml (working copy) | ||
---|---|---|
2 | 2 |
<thead> |
3 | 3 |
<tr> |
4 | 4 |
<%= sort_header_tag('spent_on', :caption => l(:label_date), :default_order => 'desc') %> |
5 |
<%= sort_header_tag('start_time', :caption => l(:field_start_time), :default_order => 'desc') %> |
|
6 |
<%= sort_header_tag('end_time', :caption => l(:field_end_time), :default_order => 'desc') %> |
|
7 |
<%= sort_header_tag('hours', :caption => l(:field_hours)) %> |
|
5 | 8 |
<%= sort_header_tag('user_id', :caption => l(:label_member)) %> |
6 | 9 |
<%= sort_header_tag('activity_id', :caption => l(:label_activity)) %> |
7 | 10 |
<%= sort_header_tag("#{Project.table_name}.name", :caption => l(:label_project)) %> |
8 | 11 |
<%= sort_header_tag('issue_id', :caption => l(:label_issue), :default_order => 'desc') %> |
9 | 12 |
<th><%= l(:field_comments) %></th> |
10 |
<%= sort_header_tag('hours', :caption => l(:field_hours)) %> |
|
11 | 13 |
<th></th> |
12 | 14 |
</tr> |
13 | 15 |
</thead> |
... | ... | |
15 | 17 |
<% entries.each do |entry| -%> |
16 | 18 |
<tr class="time-entry <%= cycle("odd", "even") %>"> |
17 | 19 |
<td class="spent_on"><%= format_date(entry.spent_on) %></td> |
20 |
<td class="start_time"><%= format_time(entry.start_time) %></td> |
|
21 |
<td class="end_time"><%= format_time(entry.end_time) %></td> |
|
22 |
<td class="hours"><%= hours(entry) %></td> |
|
18 | 23 |
<td class="user"><%=h entry.user %></td> |
19 | 24 |
<td class="activity"><%=h entry.activity %></td> |
20 | 25 |
<td class="project"><%=h entry.project %></td> |
... | ... | |
24 | 29 |
<% end -%> |
25 | 30 |
</td> |
26 | 31 |
<td class="comments"><%=h entry.comments %></td> |
27 |
<td class="hours"><%= html_hours("%.2f" % entry.hours) %></td> |
|
28 | 32 |
<td align="center"> |
29 | 33 |
<% if entry.editable_by?(User.current) -%> |
30 | 34 |
<%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => entry}, |
app/views/timelog/edit.rhtml (working copy) | ||
---|---|---|
3 | 3 |
<% labelled_tabular_form_for :time_entry, @time_entry, :url => {:action => 'edit', :project_id => @time_entry.project} do |f| %> |
4 | 4 |
<%= error_messages_for 'time_entry' %> |
5 | 5 |
<%= back_url_hidden_field_tag %> |
6 |
<% content_for :header_tags do %> |
|
7 |
<%= javascript_include_tag 'time' %> |
|
8 |
<% end %> |
|
6 | 9 |
|
10 |
<% content_for :header_tags do %> |
|
11 |
<script language="javascript"> |
|
12 |
window.onload = function() { |
|
13 |
$('<%= @activate_field %>').activate(); |
|
14 |
}; |
|
15 |
</script> |
|
16 |
<% end %> |
|
17 |
|
|
7 | 18 |
<div class="box"> |
8 | 19 |
<p><%= f.text_field :issue_id, :size => 6 %> <em><%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %></em></p> |
9 | 20 |
<p><%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p> |
10 |
<p><%= f.text_field :hours, :size => 6, :required => true %></p> |
|
21 |
<p><%= f.time_field :start_time %></p> |
|
22 |
<p><%= f.time_field :end_time %></p> |
|
23 |
<p><%= f.text_field :hours, :size => 6 %> <em> <%= l(:text_clear_to_recalculate_time_by_range) %></em> </p> |
|
11 | 24 |
<p><%= f.text_field :comments, :size => 100 %></p> |
12 | 25 |
<p><%= f.select :activity_id, activity_collection_for_select_options, :required => true %></p> |
13 | 26 |
</div> |
lib/tabular_form_builder.rb (working copy) | ||
---|---|---|
40 | 40 |
END_SRC |
41 | 41 |
class_eval src, __FILE__, __LINE__ |
42 | 42 |
end |
43 |
|
|
44 |
def time_field(field) |
|
45 |
value = nil |
|
46 |
value = @object.send(field) if @object |
|
47 |
value = value.strftime('%Y-%m-%d %H:%M') if value |
|
48 |
result = text_field field, :size => 15, :value => value |
|
49 |
result += (' <input type="button" onclick="maybeSetTimeNow($(\'%s\'))' + |
|
50 |
'" value="' + l(:button_now) + |
|
51 |
'" name="action"/>') % "#{@object_name}_#{field}" |
|
52 |
result |
|
53 |
end |
|
43 | 54 |
|
44 | 55 |
def select(field, choices, options = {}, html_options = {}) |
45 | 56 |
label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") |
app/controllers/application.rb (working copy) | ||
---|---|---|
219 | 219 |
def filename_for_content_disposition(name) |
220 | 220 |
request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name |
221 | 221 |
end |
222 | ||
223 |
def url_path(url_parameters) |
|
224 |
rs = ::ActionController::Routing::Routes |
|
225 |
rs.generate url_parameters |
|
226 |
end |
|
222 | 227 |
end |
app/controllers/issues_controller.rb (working copy) | ||
---|---|---|
101 | 101 |
@allowed_statuses = @issue.new_statuses_allowed_to(User.current) |
102 | 102 |
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) |
103 | 103 |
@priorities = Enumeration::get_values('IPRI') |
104 |
@time_entry = TimeEntry.new |
|
104 |
@time_entry = @issue.time_entry_in_progress(User.current) || TimeEntry.new
|
|
105 | 105 |
respond_to do |format| |
106 | 106 |
format.html { render :template => 'issues/show.rhtml' } |
107 | 107 |
format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' } |
... | ... | |
172 | 172 |
end |
173 | 173 | |
174 | 174 |
if request.post? |
175 |
@time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today) |
|
175 |
@time_entry = TimeEntry.find_by_id(params[:time_entry][:id]) if params[:time_entry] |
|
176 |
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, |
|
177 |
:user => User.current, :spent_on => Date.today) |
|
178 |
|
|
176 | 179 |
@time_entry.attributes = params[:time_entry] |
177 | 180 |
attachments = attach_files(@issue, params[:attachments]) |
178 | 181 |
attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)} |
app/controllers/timelog_controller.rb (working copy) | ||
---|---|---|
175 | 175 |
end |
176 | 176 |
|
177 | 177 |
def edit |
178 |
return if redirect_if_in_progress |
|
179 |
|
|
178 | 180 |
render_403 and return if @time_entry && !@time_entry.editable_by?(User.current) |
179 | 181 |
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today) |
180 | 182 |
@time_entry.attributes = params[:time_entry] |
183 |
if !@time_entry.hours |
|
184 |
if !@time_entry.start_time |
|
185 |
@activate_field = 'time_entry_start_time' |
|
186 |
else |
|
187 |
@activate_field = 'time_entry_end_time' |
|
188 |
end |
|
189 |
end |
|
190 |
|
|
191 |
url_writer = lambda do |entry| |
|
192 |
"<a href = \"#{url_path(:controller => :timelog, :action => :edit, |
|
193 |
:id => entry.id)}\">##{entry.issue_id}-#{entry.id}</a>" |
|
194 |
end |
|
195 |
|
|
181 | 196 |
if request.post? and @time_entry.save |
182 |
flash[:notice] = l(:notice_successful_update) |
|
197 |
intersecting = @time_entry.find_intersecting_entries |
|
198 |
logger.debug "intersecting = #{intersecting.inspect}" |
|
199 |
msg = l(:notice_successful_update) |
|
200 |
if !intersecting.empty? |
|
201 |
|
|
202 |
list = lwr(:text_time_entry_intersecting_notice_entry, |
|
203 |
intersecting.size) + ' ' + intersecting. |
|
204 |
map { |entry| url_writer.call(entry) }. |
|
205 |
to_sentence(:skip_last_comma => true, :connector => l(:text_and)) |
|
206 |
|
|
207 |
msg += ' ' + l(:text_time_entry_intersecting_notice, |
|
208 |
url_writer.call(@time_entry), list) |
|
209 |
end |
|
210 |
flash[:notice] = msg |
|
183 | 211 |
redirect_to(params[:back_url].blank? ? {:action => 'details', :project_id => @time_entry.project} : params[:back_url]) |
184 | 212 |
return |
185 | 213 |
end |
... | ... | |
258 | 286 |
@from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today) - 1 |
259 | 287 |
@to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today) |
260 | 288 |
end |
289 |
|
|
290 |
def redirect_if_in_progress |
|
291 |
if !@time_entry && @issue |
|
292 |
in_progress_entry = @issue.time_entry_in_progress(User.current) |
|
293 |
if in_progress_entry |
|
294 |
#in order to avoid :id form parameter and not complicate :find_project filter |
|
295 |
redirect_to(:controller => 'timelog', :action => 'edit', |
|
296 |
:id => in_progress_entry) |
|
297 |
return true |
|
298 |
end |
|
299 |
end |
|
300 |
false |
|
301 |
end |
|
261 | 302 |
end |
app/helpers/application_helper.rb (working copy) | ||
---|---|---|
70 | 70 |
@date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format) |
71 | 71 |
date.strftime(@date_format) |
72 | 72 |
end |
73 |
|
|
73 | ||
74 | 74 |
def format_time(time, include_date = true) |
75 | 75 |
return nil unless time |
76 | 76 |
time = time.to_time if time.is_a?(String) |
... | ... | |
78 | 78 |
local = zone ? time.in_time_zone(zone) : (time.utc? ? time.utc_to_local : time) |
79 | 79 |
@date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format) |
80 | 80 |
@time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format) |
81 |
include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format) |
|
81 |
format = include_date ? "#{@date_format} #{@time_format}" : @time_format |
|
82 |
local.strftime(format) |
|
82 | 83 |
end |
83 | 84 |
|
84 | 85 |
# Truncates and returns the string as a single line |
app/helpers/issues_helper.rb (working copy) | ||
---|---|---|
181 | 181 |
export.rewind |
182 | 182 |
export |
183 | 183 |
end |
184 |
|
|
185 |
def spent_hours(issue) |
|
186 |
return '-' if issue.time_entries.empty? |
|
187 |
spent_hours = lwr(:label_f_hour, issue.spent_hours) |
|
188 |
spent_hours += " (#{l(:text_in_progress)})" if issue.time_entry_in_progress |
|
189 |
link_to spent_hours, {:controller => 'timelog', :action => 'details', :project_id => issue.project, :issue_id => issue}, :class => 'icon icon-time' |
|
190 |
end |
|
184 | 191 |
end |
app/helpers/timelog_helper.rb (working copy) | ||
---|---|---|
36 | 36 |
sum |
37 | 37 |
end |
38 | 38 |
|
39 |
def hours(entry) |
|
40 |
return '<span class="hours hours-dec">[%s]</span>' % l(:text_in_progress) if !entry.hours |
|
41 |
html_hours("%.2f" % entry.hours) |
|
42 |
end |
|
43 |
|
|
39 | 44 |
def options_for_period_select(value) |
40 | 45 |
options_for_select([[l(:label_all_time), 'all'], |
41 | 46 |
[l(:label_today), 'today'], |
app/models/issue.rb (working copy) | ||
---|---|---|
28 | 28 |
has_many :journals, :as => :journalized, :dependent => :destroy |
29 | 29 |
has_many :attachments, :as => :container, :dependent => :destroy |
30 | 30 |
has_many :time_entries, :dependent => :delete_all |
31 |
|
|
31 | 32 |
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC" |
32 | 33 |
|
33 | 34 |
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all |
... | ... | |
251 | 252 |
def to_s |
252 | 253 |
"#{tracker} ##{id}: #{subject}" |
253 | 254 |
end |
255 |
|
|
256 |
def time_entry_in_progress(user = nil) |
|
257 |
TimeEntry.find_by_issue_id(self.id, |
|
258 |
:conditions => 'start_time IS NOT NULL and hours IS NULL' + (user ? " and user_id = #{user.id}" : '')) |
|
259 |
end |
|
254 | 260 |
end |
app/models/time_entry.rb (working copy) | ||
---|---|---|
25 | 25 |
|
26 | 26 |
attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek |
27 | 27 | |
28 |
acts_as_event :title => Proc.new {|o| "#{o.user}: #{lwr(:label_f_hour, o.hours)} (#{(o.issue || o.project).event_title})"}, |
|
29 |
:url => Proc.new {|o| {:controller => 'timelog', :action => 'details', :project_id => o.project}}, |
|
28 |
title_proc = Proc.new do |entry| |
|
29 |
hours = entry.hours ? lwr(:label_f_hour, entry.hours) : "[#{l(:text_in_progress)}]" |
|
30 |
"#{entry.user}: #{hours} (#{(entry.issue || entry.project).event_title})" |
|
31 |
end |
|
32 |
|
|
33 |
acts_as_event :title => title_proc, |
|
34 |
:url => Proc.new {|entry| {:controller => 'timelog', :action => 'details', :project_id => entry.project}}, |
|
30 | 35 |
:author => :user, |
31 | 36 |
:description => :comments |
32 | 37 |
|
33 |
validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
|
|
38 |
validates_presence_of :user_id, :activity_id, :project_id, :spent_on |
|
34 | 39 |
validates_numericality_of :hours, :allow_nil => true |
35 | 40 |
validates_length_of :comments, :maximum => 255, :allow_nil => true |
36 | 41 | |
... | ... | |
48 | 53 |
|
49 | 54 |
def validate |
50 | 55 |
errors.add :hours, :activerecord_error_invalid if hours && (hours < 0 || hours >= 1000) |
56 |
|
|
57 |
if !start_time && !hours |
|
58 |
#rather verbose, but l() always translate to English here for some reason |
|
59 |
errors.add :hours, ll(User.current.language, |
|
60 |
:activerecord_error_field_must_be_set_if_other_is_not, |
|
61 |
ll(User.current.language, :field_start_time)) |
|
62 | ||
63 |
errors.add :start_time, ll(User.current.language, |
|
64 |
:activerecord_error_field_must_be_set_if_other_is_not, |
|
65 |
ll(User.current.language, :field_hours)) |
|
66 |
end |
|
67 |
|
|
51 | 68 |
errors.add :project_id, :activerecord_error_invalid if project.nil? |
52 | 69 |
errors.add :issue_id, :activerecord_error_invalid if (issue_id && !issue) || (issue && project!=issue.project) |
53 | 70 |
end |
... | ... | |
75 | 92 |
yield |
76 | 93 |
end |
77 | 94 |
end |
95 |
|
|
96 |
def before_save |
|
97 |
if !hours && start_time && end_time |
|
98 |
self.hours = (end_time - start_time) / 3600 |
|
99 |
end |
|
100 |
end |
|
101 |
|
|
102 |
def find_intersecting_entries |
|
103 |
params = {:start_time => start_time, :end_time => end_time, :id => id} |
|
104 |
|
|
105 |
self.class.find_all_by_user_id(user_id, :conditions => |
|
106 |
["(" + |
|
107 |
#this entry's start time or end time is between other's start_time and end_time |
|
108 |
"start_time < :start_time and :start_time < end_time OR " + |
|
109 |
"start_time < :end_time and :end_time < end_time OR " + |
|
110 |
#other's entry's start time or end time is between this entry's start_time and end_time |
|
111 |
"start_time > :start_time and start_time < :end_time OR " + |
|
112 |
"end_time > :start_time and end_time < :end_time" + |
|
113 |
")" + |
|
114 |
"and id <> :id", params]) |
|
115 |
end |
|
78 | 116 |
end |
lang/bg.yml (working copy) | ||
---|---|---|
632 | 632 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
633 | 633 |
setting_mail_handler_api_key: API key |
634 | 634 |
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." |
635 |
field_start_time: Start Time |
|
636 |
field_end_time: End Time |
|
637 |
text_in_progress: in progress |
|
638 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
639 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
640 |
text_and: and |
|
641 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
642 |
text_time_entry_intersecting_notice_entry: entry |
|
643 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
644 |
button_now: Now |
lang/cs.yml (working copy) | ||
---|---|---|
637 | 637 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
638 | 638 |
setting_mail_handler_api_key: API key |
639 | 639 |
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." |
640 |
field_start_time: Start Time |
|
641 |
field_end_time: End Time |
|
642 |
text_in_progress: in progress |
|
643 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
644 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
645 |
text_and: and |
|
646 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
647 |
text_time_entry_intersecting_notice_entry: entry |
|
648 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
649 |
button_now: Now |
lang/da.yml (working copy) | ||
---|---|---|
634 | 634 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
635 | 635 |
setting_mail_handler_api_key: API key |
636 | 636 |
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." |
637 |
field_start_time: Start Time |
|
638 |
field_end_time: End Time |
|
639 |
text_in_progress: in progress |
|
640 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
641 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
642 |
text_and: and |
|
643 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
644 |
text_time_entry_intersecting_notice_entry: entry |
|
645 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
646 |
button_now: Now |
lang/de.yml (working copy) | ||
---|---|---|
633 | 633 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
634 | 634 |
setting_mail_handler_api_key: API key |
635 | 635 |
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." |
636 |
field_start_time: Start Time |
|
637 |
field_end_time: End Time |
|
638 |
text_in_progress: in progress |
|
639 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
640 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
641 |
text_and: and |
|
642 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
643 |
text_time_entry_intersecting_notice_entry: entry |
|
644 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
645 |
button_now: Now |
lang/en.yml (working copy) | ||
---|---|---|
170 | 170 |
field_hours: Hours |
171 | 171 |
field_activity: Activity |
172 | 172 |
field_spent_on: Date |
173 |
field_start_time: Start Time |
|
174 |
field_end_time: End Time |
|
173 | 175 |
field_identifier: Identifier |
174 | 176 |
field_is_filter: Used as a filter |
175 | 177 |
field_issue_to_id: Related issue |
... | ... | |
558 | 560 |
button_annotate: Annotate |
559 | 561 |
button_update: Update |
560 | 562 |
button_configure: Configure |
563 |
button_now: Now |
|
561 | 564 | |
562 | 565 |
status_active: active |
563 | 566 |
status_registered: registered |
... | ... | |
633 | 636 |
enumeration_issue_priorities: Issue priorities |
634 | 637 |
enumeration_doc_categories: Document categories |
635 | 638 |
enumeration_activities: Activities (time tracking) |
639 |
text_in_progress: in progress |
|
640 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
641 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
642 |
text_and: and |
|
643 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
644 |
text_time_entry_intersecting_notice_entry: entry |
|
645 |
text_time_entry_intersecting_notice_entry_plural: entries |
lang/es.yml (working copy) | ||
---|---|---|
635 | 635 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
636 | 636 |
setting_mail_handler_api_key: API key |
637 | 637 |
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." |
638 |
field_start_time: Start Time |
|
639 |
field_end_time: End Time |
|
640 |
text_in_progress: in progress |
|
641 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
642 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
643 |
text_and: and |
|
644 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
645 |
text_time_entry_intersecting_notice_entry: entry |
|
646 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
647 |
button_now: Now |
lang/fi.yml (working copy) | ||
---|---|---|
632 | 632 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
633 | 633 |
setting_mail_handler_api_key: API key |
634 | 634 |
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." |
635 |
field_start_time: Start Time |
|
636 |
field_end_time: End Time |
|
637 |
text_in_progress: in progress |
|
638 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
639 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
640 |
text_and: and |
|
641 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
642 |
text_time_entry_intersecting_notice_entry: entry |
|
643 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
644 |
button_now: Now |
lang/fr.yml (working copy) | ||
---|---|---|
633 | 633 |
enumeration_issue_priorities: Priorités des demandes |
634 | 634 |
enumeration_doc_categories: Catégories des documents |
635 | 635 |
enumeration_activities: Activités (suivi du temps) |
636 |
field_start_time: Start Time |
|
637 |
field_end_time: End Time |
|
638 |
text_in_progress: in progress |
|
639 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
640 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
641 |
text_and: and |
|
642 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
643 |
text_time_entry_intersecting_notice_entry: entry |
|
644 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
645 |
button_now: Now |
lang/he.yml (working copy) | ||
---|---|---|
632 | 632 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
633 | 633 |
setting_mail_handler_api_key: API key |
634 | 634 |
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." |
635 |
field_start_time: Start Time |
|
636 |
field_end_time: End Time |
|
637 |
text_in_progress: in progress |
|
638 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
639 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
640 |
text_and: and |
|
641 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
642 |
text_time_entry_intersecting_notice_entry: entry |
|
643 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
644 |
button_now: Now |
lang/hu.yml (working copy) | ||
---|---|---|
633 | 633 |
setting_mail_handler_api_enabled: Web Service engedélyezése a beérkezett levelekhez |
634 | 634 |
setting_mail_handler_api_key: API kulcs |
635 | 635 |
text_email_delivery_not_configured: "Az E-mail küldés nincs konfigurálva, és az értesítések ki vannak kapcsolva.\nÁllítsd be az SMTP szervert a config/email.yml fájlban és indítsd újra az alkalmazást, hogy érvénybe lépjen." |
636 |
field_start_time: Start Time |
|
637 |
field_end_time: End Time |
|
638 |
text_in_progress: in progress |
|
639 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
640 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
641 |
text_and: and |
|
642 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
643 |
text_time_entry_intersecting_notice_entry: entry |
|
644 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
645 |
button_now: Now |
lang/it.yml (working copy) | ||
---|---|---|
632 | 632 |
setting_mail_handler_api_enabled: Abilita WS per le e-mail in arrivo |
633 | 633 |
setting_mail_handler_api_key: chiave API |
634 | 634 |
text_email_delivery_not_configured: "La consegna via e-mail non è configurata e le notifiche sono disabilitate.\nConfigura il tuo server SMTP in config/email.yml e riavvia l'applicazione per abilitarle." |
635 |
field_start_time: Start Time |
|
636 |
field_end_time: End Time |
|
637 |
text_in_progress: in progress |
|
638 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
639 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
640 |
text_and: and |
|
641 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
642 |
text_time_entry_intersecting_notice_entry: entry |
|
643 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
644 |
button_now: Now |
lang/ja.yml (working copy) | ||
---|---|---|
633 | 633 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
634 | 634 |
setting_mail_handler_api_key: API key |
635 | 635 |
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." |
636 |
field_start_time: Start Time |
|
637 |
field_end_time: End Time |
|
638 |
text_in_progress: in progress |
|
639 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
640 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
641 |
text_and: and |
|
642 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
643 |
text_time_entry_intersecting_notice_entry: entry |
|
644 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
645 |
button_now: Now |
lang/ko.yml (working copy) | ||
---|---|---|
632 | 632 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
633 | 633 |
setting_mail_handler_api_key: API key |
634 | 634 |
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." |
635 |
field_start_time: Start Time |
|
636 |
field_end_time: End Time |
|
637 |
text_in_progress: in progress |
|
638 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
639 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
640 |
text_and: and |
|
641 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
642 |
text_time_entry_intersecting_notice_entry: entry |
|
643 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
644 |
button_now: Now |
lang/lt.yml (working copy) | ||
---|---|---|
635 | 635 |
setting_mail_handler_api_key: API raktas |
636 | 636 | |
637 | 637 |
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." |
638 |
field_start_time: Start Time |
|
639 |
field_end_time: End Time |
|
640 |
text_in_progress: in progress |
|
641 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
642 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
643 |
text_and: and |
|
644 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
645 |
text_time_entry_intersecting_notice_entry: entry |
|
646 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
647 |
button_now: Now |
lang/nl.yml (working copy) | ||
---|---|---|
633 | 633 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
634 | 634 |
setting_mail_handler_api_key: API key |
635 | 635 |
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." |
636 |
field_start_time: Start Time |
|
637 |
field_end_time: End Time |
|
638 |
text_in_progress: in progress |
|
639 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
640 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
641 |
text_and: and |
|
642 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
643 |
text_time_entry_intersecting_notice_entry: entry |
|
644 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
645 |
button_now: Now |
lang/no.yml (working copy) | ||
---|---|---|
633 | 633 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
634 | 634 |
setting_mail_handler_api_key: API key |
635 | 635 |
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." |
636 |
field_start_time: Start Time |
|
637 |
field_end_time: End Time |
|
638 |
text_in_progress: in progress |
|
639 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
640 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
641 |
text_and: and |
|
642 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
643 |
text_time_entry_intersecting_notice_entry: entry |
|
644 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
645 |
button_now: Now |
lang/pl.yml (working copy) | ||
---|---|---|
632 | 632 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
633 | 633 |
setting_mail_handler_api_key: API key |
634 | 634 |
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." |
635 |
field_start_time: Start Time |
|
636 |
field_end_time: End Time |
|
637 |
text_in_progress: in progress |
|
638 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
639 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
640 |
text_and: and |
|
641 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
642 |
text_time_entry_intersecting_notice_entry: entry |
|
643 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
644 |
button_now: Now |
lang/pt-br.yml (working copy) | ||
---|---|---|
632 | 632 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
633 | 633 |
setting_mail_handler_api_key: API key |
634 | 634 |
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." |
635 |
field_start_time: Start Time |
|
636 |
field_end_time: End Time |
|
637 |
text_in_progress: in progress |
|
638 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
639 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
640 |
text_and: and |
|
641 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
642 |
text_time_entry_intersecting_notice_entry: entry |
|
643 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
644 |
button_now: Now |
lang/pt.yml (working copy) | ||
---|---|---|
632 | 632 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
633 | 633 |
setting_mail_handler_api_key: API key |
634 | 634 |
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." |
635 |
field_start_time: Start Time |
|
636 |
field_end_time: End Time |
|
637 |
text_in_progress: in progress |
|
638 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
639 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
640 |
text_and: and |
|
641 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
642 |
text_time_entry_intersecting_notice_entry: entry |
|
643 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
644 |
button_now: Now |
lang/ro.yml (working copy) | ||
---|---|---|
632 | 632 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
633 | 633 |
setting_mail_handler_api_key: API key |
634 | 634 |
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." |
635 |
field_start_time: Start Time |
|
636 |
field_end_time: End Time |
|
637 |
text_in_progress: in progress |
|
638 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
639 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
640 |
text_and: and |
|
641 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
642 |
text_time_entry_intersecting_notice_entry: entry |
|
643 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
644 |
button_now: Now |
lang/ru.yml (working copy) | ||
---|---|---|
164 | 164 |
field_url: URL |
165 | 165 |
field_start_page: Стартовая страница |
166 | 166 |
field_subproject: Подпроект |
167 |
field_hours: Час(а,ов)
|
|
167 |
field_hours: Часов
|
|
168 | 168 |
field_activity: Деятельность |
169 | 169 |
field_spent_on: Дата |
170 |
field_start_time: Время начала |
|
171 |
field_end_time: Время окончания |
|
170 | 172 |
field_identifier: Ун. идентификатор |
171 | 173 |
field_is_filter: Используется в качестве фильтра |
172 | 174 |
field_issue_to_id: Связанные задачи |
... | ... | |
395 | 397 |
label_issue_tracking: Ситуация по задачам |
396 | 398 |
label_spent_time: Затраченное время |
397 | 399 |
label_f_hour: %.2f час |
398 |
label_f_hour_plural: %.2f часов(а)
|
|
400 |
label_f_hour_plural: %.2f часов |
|
399 | 401 |
label_time_tracking: Учет времени |
400 | 402 |
label_change_plural: Правки |
401 | 403 |
label_statistics: Статистика |
... | ... | |
508 | 510 |
button_copy: Копировать |
509 | 511 |
button_annotate: Авторство |
510 | 512 |
button_update: Обновить |
513 |
button_now: Сейчас |
|
511 | 514 | |
512 | 515 |
status_active: Активен |
513 | 516 |
status_registered: Зарегистрирован |
... | ... | |
636 | 639 |
setting_mail_handler_api_enabled: Включить веб-сервис для входящих сообщений |
637 | 640 |
setting_mail_handler_api_key: API ключ |
638 | 641 |
text_email_delivery_not_configured: "Параметры работы с почтовым сервером не настроены и функция уведомления по email не активна.\nНастроить параметры для вашего SMTP сервера вы можете в файле config/email.yml. Для применения изменений перезапустите приложение." |
642 |
text_in_progress: в процессе |
|
643 |
activerecord_error_field_must_be_set_if_other_is_not: необходимо задать, если значение поля '%s' не задано |
|
644 |
text_clear_to_recalculate_time_by_range: Очистите, чтобы пересчитать по времени окончания |
|
645 |
text_and: и |
|
646 |
text_time_entry_intersecting_notice: Обратите внимание, что эта запись о потраченном времени (%s) пересекается с %s. |
|
647 |
text_time_entry_intersecting_notice_entry: записью |
|
648 |
text_time_entry_intersecting_notice_entry_plural: записями |
lang/sr.yml (working copy) | ||
---|---|---|
633 | 633 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
634 | 634 |
setting_mail_handler_api_key: API key |
635 | 635 |
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." |
636 |
field_start_time: Start Time |
|
637 |
field_end_time: End Time |
|
638 |
text_in_progress: in progress |
|
639 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
640 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
641 |
text_and: and |
|
642 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
643 |
text_time_entry_intersecting_notice_entry: entry |
|
644 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
645 |
button_now: Now |
lang/sv.yml (working copy) | ||
---|---|---|
633 | 633 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
634 | 634 |
setting_mail_handler_api_key: API key |
635 | 635 |
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." |
636 |
field_start_time: Start Time |
|
637 |
field_end_time: End Time |
|
638 |
text_in_progress: in progress |
|
639 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
640 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
641 |
text_and: and |
|
642 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
643 |
text_time_entry_intersecting_notice_entry: entry |
|
644 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
645 |
button_now: Now |
lang/th.yml (working copy) | ||
---|---|---|
635 | 635 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
636 | 636 |
setting_mail_handler_api_key: API key |
637 | 637 |
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." |
638 |
field_start_time: Start Time |
|
639 |
field_end_time: End Time |
|
640 |
text_in_progress: in progress |
|
641 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
642 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
643 |
text_and: and |
|
644 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
645 |
text_time_entry_intersecting_notice_entry: entry |
|
646 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
647 |
button_now: Now |
lang/uk.yml (working copy) | ||
---|---|---|
634 | 634 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
635 | 635 |
setting_mail_handler_api_key: API key |
636 | 636 |
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." |
637 |
field_start_time: Start Time |
|
638 |
field_end_time: End Time |
|
639 |
text_in_progress: in progress |
|
640 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
641 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
642 |
text_and: and |
|
643 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
644 |
text_time_entry_intersecting_notice_entry: entry |
|
645 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
646 |
button_now: Now |
lang/zh-tw.yml (working copy) | ||
---|---|---|
633 | 633 |
enumeration_issue_priorities: 項目優先權 |
634 | 634 |
enumeration_doc_categories: 文件分類 |
635 | 635 |
enumeration_activities: 活動 (時間追蹤) |
636 |
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." |
|
637 |
field_start_time: Start Time |
|
638 |
field_end_time: End Time |
|
639 |
text_in_progress: in progress |
|
640 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
641 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
642 |
text_and: and |
|
643 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
644 |
text_time_entry_intersecting_notice_entry: entry |
|
645 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
646 |
button_now: Now |
lang/zh.yml (working copy) | ||
---|---|---|
633 | 633 |
enumeration_doc_categories: 文档类别 |
634 | 634 |
enumeration_activities: 活动(时间跟踪) |
635 | 635 |
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." |
636 |
field_start_time: Start Time |
|
637 |
field_end_time: End Time |
|
638 |
text_in_progress: in progress |
|
639 |
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not |
|
640 |
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time |
|
641 |
text_and: and |
|
642 |
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s. |
|
643 |
text_time_entry_intersecting_notice_entry: entry |
|
644 |
text_time_entry_intersecting_notice_entry_plural: entries |
|
645 |
button_now: Now |
public/javascripts/time.js (revision 0) | ||
---|---|---|
1 |
//taken from XPlanner |
|
2 |
var dateFormatChars = "dMyhHmsa"; |
|
3 |
|
|
4 |
function formatDate(date, format) { |
|
5 |
return formatDate2(date, format, 0); |
|
6 |
} |
|
7 |
|
|
8 |
function formatDate2(date, format, offset) { |
|
9 |
if (offset >= format.length) { |
|
10 |
return ""; |
|
11 |
} else if (dateFormatChars.indexOf(format.charAt(offset)) != -1) { |
|
12 |
return formatDateElement(date, format, offset); |
|
13 |
} else { |
|
14 |
return formatDateLiteral(date, format, offset); |
|
15 |
} |
|
16 |
} |
|
17 |
|
|
18 |
function formatDateElement(date, format, offset) { |
|
19 |
var end = offset; |
|
20 |
var ch = format.charAt(offset); |
|
21 |
while (++end < format.length && format.charAt(end) == ch); |
|
22 |
var count = end - offset; |
|
23 |
var value; |
|
24 |
if (ch == 'd') { |
|
25 |
value = padValue(count, date.getDate()); |
|
26 |
} |
|
27 |
else if (ch == 'M') { |
|
28 |
value = padValue(count, date.getMonth()+1); |
|
29 |
} |
|
30 |
else if (ch == 'y') { |
|
31 |
value = padValue(count, date.getFullYear()); |
|
32 |
} |
|
33 |
else if (ch == 'H') { |
|
34 |
value = padValue(count, date.getHours()); |
|
35 |
} |
|
36 |
else if (ch == 'h') { |
|
37 |
value = padValue(count, date.getHours() % 12); |
|
38 |
} |
|
39 |
else if (ch == 'm') { |
|
40 |
value = padValue(count, date.getMinutes()); |
|
41 |
} |
|
42 |
else if (ch == 's') { |
|
43 |
value = padValue(count, date.getSeconds()); |
|
44 |
} |
|
45 |
else if (ch == 'a') { |
|
46 |
value = date.getHours() > 12 ? 'PM' : 'AM'; |
|
47 |
} |
|
48 |
return value + formatDate2(date, format, end); |
|
49 |
} |
|
50 |
|
|
51 |
function padValue(count, value) { |
|
52 |
for (i = value.toString().length; i < count; i++) { |
|
53 |
value = '0'+value; |
|
54 |
} |
|
55 |
return value; |
|
56 |
} |
|
57 |
|
|
58 |
function formatDateLiteral(date, format, offset) { |
|
59 |
end = offset; |
|
60 |
while (++end < format.length && dateFormatChars.indexOf(format.charAt(end)) == -1); |
|
61 |
return format.substr(offset, end - offset) + formatDate2(date, format, end); |
|
62 |
} |
|
63 |
|
|
64 |
function maybeSetTimeNow(field) { |
|
65 |
if (field.value == "") { |
|
66 |
field.value = formatDate(new Date(), "yyyy-MM-dd HH:mm"); |
|
67 |
} |
|
68 |
} |
test/fixtures/time_entries.yml (working copy) | ||
---|---|---|
55 | 55 |
hours: 7.65 |
56 | 56 |
user_id: 1 |
57 | 57 |
tyear: 2007 |
58 |
|
|
58 |
|
|
59 |
#this is to run existing controller tests with Start/End time improvement - specific entries |
|
60 |
time_entry_in_progress: |
|
61 |
created_on: 2007-04-22 12:20:48 +02:00 |
|
62 |
tweek: 16 |
|
63 |
tmonth: 4 |
|
64 |
project_id: 1 |
|
65 |
comments: Time spent on a subproject (in progress) |
|
66 |
updated_on: 2007-04-22 12:20:48 +02:00 |
|
67 |
activity_id: 10 |
|
68 |
spent_on: 2007-04-22 |
|
69 |
issue_id: 1 |
|
70 |
id: 5 |
|
71 |
user_id: 1 |
|
72 |
tyear: 2007 |
|
73 |
start_time: 2007-07-11 16:20 |
|
74 |
|
|
75 |
#same user but another issue and project, and intersects with time_entry_in_progress |
|
76 |
intersecting_time_entry: |
|
77 |
created_on: 2007-04-22 12:20:48 +02:00 |
|
78 |
tweek: 16 |
|
79 |
tmonth: 4 |
|
80 |
project_id: 2 |
|
81 |
comments: Time entry intersecting with time_entry_in_progress |
|
82 |
updated_on: 2007-04-22 12:20:48 +02:00 |
|
83 |
activity_id: 10 |
|
84 |
spent_on: 2007-04-22 |
|
85 |
issue_id: 4 |
|
86 |
id: 6 |
|
87 |
user_id: 1 |
|
88 |
tyear: 2007 |
|
89 |
start_time: 2007-07-11 15:20 |
|
90 |
end_time: 2007-07-11 17:20 |
|
91 |
hours: 0 #to not break time calculation tests |
|
92 |
|
|
93 |
#this spans several years - should intersect with time_entry_in_progress as well |
|
94 |
big_intersecting_time_entry: |
|
95 |
created_on: 2007-04-22 12:20:48 +02:00 |
|
96 |
tweek: 16 |
|
97 |
tmonth: 4 |
|
98 |
project_id: 1 |
|
99 |
comments: Time entry intersecting with time_entry_in_progress |
|
100 |
updated_on: 2007-04-22 12:20:48 +02:00 |
|
101 |
activity_id: 10 |
|
102 |
spent_on: 2007-04-22 |
|
103 |
issue_id: 1 |
|
104 |
id: 7 |
|
105 |
user_id: 1 |
|
106 |
tyear: 2007 |
|
107 |
start_time: 2005-07-11 16:30 |
|
108 |
end_time: 2007-07-11 17:20 |
|
109 |
hours: 0 #to not break time calculation tests |
test/functional/issues_controller_test.rb (working copy) | ||
---|---|---|
164 | 164 |
:content => /Notes/ } } |
165 | 165 |
end |
166 | 166 | |
167 |
def test_show_in_progress |
|
168 |
@request.session[:user_id] = 1 |
|
169 |
get :show, :id => 1 |
|
170 |
assert_equal time_entries(:time_entry_in_progress), assigns(:time_entry), |
|
171 |
'there should be an in-progress time entry for this user' |
|
172 |
end |
|
173 |
|
|
174 |
def test_show_not_in_progress |
|
175 |
@request.session[:user_id] = 2 |
|
176 |
get :show, :id => 1 |
|
177 |
assert_nil assigns(:time_entry).id, 'time entry for this user should be new' |
|
178 |
end |
|
179 |
|
|
167 | 180 |
def test_get_new |
168 | 181 |
@request.session[:user_id] = 2 |
169 | 182 |
get :new, :project_id => 1, :tracker_id => 1 |
... | ... | |
381 | 394 |
issue = Issue.find(1) |
382 | 395 |
assert_equal 1, issue.status_id |
383 | 396 |
@request.session[:user_id] = 2 |
384 |
assert_difference('TimeEntry.count', 0) do |
|
397 |
assert_difference('TimeEntry.count', 0) do #time entries count should not change because given entry is empty (hence invalid)
|
|
385 | 398 |
post :edit, |
386 | 399 |
:id => 1, |
387 | 400 |
:issue => { :status_id => 2, :assigned_to_id => 3 }, |
... | ... | |
399 | 412 |
assert mail.body.include?("Status changed from New to Assigned") |
400 | 413 |
end |
401 | 414 |
|
415 |
def test_post_edit_with_task_in_progress |
|
416 |
user_id = 1 |
|
417 |
@request.session[:user_id] = user_id |
|
418 |
issue = Issue.find(1) |
|
419 |
time_entry = issue.time_entry_in_progress(User.find(user_id)) |
|
420 |
assert_not_nil time_entry, 'there should be time entry in progress' |
|
421 |
assert_difference('TimeEntry.count', 0) do #time entries count should not change because entry in progress should be updated |
|
422 |
post :edit, |
|
423 |
:id => 1, |
|
424 |
:issue => { }, |
|
425 |
:notes => 'Assigned to dlopper', |
|
426 |
:time_entry => { :id => time_entry.id, :comments => 'xyz' } |
|
427 |
end |
|
428 |
end |
|
429 |
|
|
402 | 430 |
def test_post_edit_with_note_only |
403 | 431 |
notes = 'Note added by IssuesControllerTest#test_update_with_note_only' |
404 | 432 |
# anonymous user |
... | ... | |
428 | 456 |
|
429 | 457 |
issue = Issue.find(1) |
430 | 458 |
|
431 |
j = issue.journals.find(:first, :order => 'id DESC') |
|
432 |
assert_equal '2.5 hours added', j.notes |
|
433 |
assert_equal 0, j.details.size |
|
459 |
#:first doesn't work sometimes here with MySQL 5.0.51b |
|
460 |
e = issue.journals.find(:all, :order => 'id DESC')[0] |
|
461 |
assert_equal '2.5 hours added', e.notes |
|
462 |
assert_equal 0, e.details.size |
|
434 | 463 |
|
435 |
t = issue.time_entries.find(:first, :order => 'id DESC') |
|
464 |
#:first doesn't work sometimes here with MySQL 5.0.51b |
|
465 |
t = issue.time_entries.find(:all, :order => 'id DESC')[0] |
|
436 | 466 |
assert_not_nil t |
437 | 467 |
assert_equal 2.5, t.hours |
438 | 468 |
assert_equal spent_hours_before + 2.5, issue.spent_hours |
test/functional/timelog_controller_test.rb (working copy) | ||
---|---|---|
40 | 40 |
:content => 'Development' |
41 | 41 |
end |
42 | 42 |
|
43 |
def test_get_edit_in_progress_by_entry_id |
|
44 |
@request.session[:user_id] = 1 #to avoid 'no permission' error |
|
45 |
get :edit, :id => time_entries(:time_entry_in_progress) |
|
46 |
assert_response :success |
|
47 |
assert_equal 'time_entry_end_time', assigns(:activate_field) |
|
48 |
end |
|
49 |
|
|
50 |
def test_get_edit_not_in_progress |
|
51 |
@request.session[:user_id] = 2 #there are no in-progress entries of this user for the issue |
|
52 |
get :edit, :issue_id => 1 |
|
53 |
assert_response :success |
|
54 |
assert_equal 'time_entry_start_time', assigns(:activate_field) |
|
55 |
end |
|
56 |
|
|
57 |
def test_get_edit_in_progress_by_issue_id |
|
58 |
user_id = 1 |
|
59 |
@request.session[:user_id] = user_id #there is in-progress entry of this user for the issue |
|
60 |
issue_id = 1 |
|
61 |
get :edit, :issue_id => issue_id |
|
62 |
assert_redirected_to :action => "edit", :id => Issue.find(issue_id).time_entry_in_progress(User.find(user_id)).id |
|
63 |
end |
|
64 |
|
|
43 | 65 |
def test_post_edit |
44 | 66 |
@request.session[:user_id] = 3 |
45 | 67 |
post :edit, :project_id => 1, |
... | ... | |
70 | 92 |
post :edit, :id => 1, |
71 | 93 |
:time_entry => {:issue_id => '2', |
72 | 94 |
:hours => '8'} |
95 |
assert_no_errors(assigns(:time_entry)) |
|
73 | 96 |
assert_redirected_to 'projects/ecookbook/timelog/details' |
74 | 97 |
entry.reload |
75 | 98 |
|
... | ... | |
165 | 188 |
assert_response :success |
166 | 189 |
assert_template 'details' |
167 | 190 |
assert_not_nil assigns(:entries) |
168 |
assert_equal 4, assigns(:entries).size
|
|
191 |
assert_equal 6, assigns(:entries).size
|
|
169 | 192 |
# project and subproject |
170 | 193 |
assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort |
171 | 194 |
assert_not_nil assigns(:total_hours) |
... | ... | |
180 | 203 |
assert_response :success |
181 | 204 |
assert_template 'details' |
182 | 205 |
assert_not_nil assigns(:entries) |
183 |
assert_equal 3, assigns(:entries).size
|
|
206 |
assert_equal 5, assigns(:entries).size
|
|
184 | 207 |
assert_not_nil assigns(:total_hours) |
185 | 208 |
assert_equal "12.90", "%.2f" % assigns(:total_hours) |
186 | 209 |
assert_equal '2007-03-20'.to_date, assigns(:from) |
... | ... | |
202 | 225 |
assert_response :success |
203 | 226 |
assert_template 'details' |
204 | 227 |
assert_not_nil assigns(:entries) |
205 |
assert_equal 2, assigns(:entries).size
|
|
228 |
assert_equal 4, assigns(:entries).size
|
|
206 | 229 |
assert_not_nil assigns(:total_hours) |
207 | 230 |
assert_equal 154.25, assigns(:total_hours) |
208 | 231 |
# display all time by default |
test/test_helper.rb (working copy) | ||
---|---|---|
64 | 64 |
Dir.mkdir "#{RAILS_ROOT}/tmp/test/attachments" unless File.directory?("#{RAILS_ROOT}/tmp/test/attachments") |
65 | 65 |
Attachment.storage_path = "#{RAILS_ROOT}/tmp/test/attachments" |
66 | 66 |
end |
67 | ||
68 |
def assert_error_on(object, field) |
|
69 |
object.valid? |
|
70 |
assert object.errors.on(field), "expected error on #{field} attribute" |
|
71 |
end |
|
72 | ||
73 |
def assert_no_error_on(object, field) |
|
74 |
object.valid? |
|
75 |
assert !object.errors.on(field), "expected no error on #{field} attribute" |
|
76 |
end |
|
77 | ||
78 |
def assert_no_errors(object, options = {}) |
|
79 |
object.valid? if options[:validate] |
|
80 |
assert_equal [], object.errors.full_messages |
|
81 |
end |
|
67 | 82 |
end |
test/unit/issue_test.rb (working copy) | ||
---|---|---|
181 | 181 |
assert_nil Issue.find_by_id(1) |
182 | 182 |
assert_nil TimeEntry.find_by_issue_id(1) |
183 | 183 |
end |
184 |
|
|
185 |
def test_find_in_progress_success |
|
186 |
assert_equal time_entries(:time_entry_in_progress), |
|
187 |
issues(:issues_001).time_entry_in_progress(users(:users_001)) |
|
188 |
end |
|
189 | ||
190 |
def test_find_in_progress_failure |
|
191 |
assert_nil issues(:issues_001).time_entry_in_progress(users(:users_002)) |
|
192 |
end |
|
184 | 193 |
end |
test/unit/time_entry_test.rb (working copy) | ||
---|---|---|
20 | 20 |
class TimeEntryTest < Test::Unit::TestCase |
21 | 21 |
fixtures :issues, :projects, :users, :time_entries |
22 | 22 | |
23 |
def setup |
|
24 |
User.current.language = 'en' |
|
25 |
end |
|
26 |
|
|
23 | 27 |
def test_hours_format |
24 | 28 |
assertions = { "2" => 2.0, |
25 | 29 |
"21.1" => 21.1, |
... | ... | |
43 | 47 |
assert_equal v, t.hours |
44 | 48 |
end |
45 | 49 |
end |
50 |
|
|
51 |
def test_start_time_must_be_set_if_hours_are_not_and_reverse |
|
52 |
entry = TimeEntry.new |
|
53 |
assert_error_on(entry, :start_time) |
|
54 |
assert_error_on(entry, :hours) |
|
55 |
end |
|
56 | ||
57 |
def test_start_time_can_be_absent_if_hours_are_set_and_reverse |
|
58 |
entry = TimeEntry.new :hours => 1 |
|
59 |
entry.valid? |
|
60 |
assert_no_error_on(entry, :start_time) |
|
61 |
entry = TimeEntry.new :start_time => Time.now |
|
62 |
entry.valid? |
|
63 |
assert_no_error_on(entry, :hours) |
|
64 |
end |
|
65 | ||
66 |
def successful_params |
|
67 |
{:spent_on => '2008-07-13', :issue_id => 1, :user => users(:users_004), |
|
68 |
:activity_id => Enumeration.get_values('ACTI').first} |
|
69 |
end |
|
70 |
|
|
71 |
def test_hours_not_calculated_if_set_explicitly |
|
72 |
#I worked on this time to time during the day, and it was 1 hour in sum |
|
73 |
entry = TimeEntry.new successful_params.merge(:hours => 1, |
|
74 |
:start_time => '2008-07-13 10:56', :end_time => '2008-07-14 10:56') |
|
75 |
|
|
76 |
entry.save! |
|
77 |
assert_equal 1, entry.hours |
|
78 |
end |
|
79 | ||
80 |
{['10:56', '11:56'] => 1, ['10:56', '11:26'] => 0.5, |
|
81 |
['10:56', '10:57'] => 0.0167, |
|
82 |
['2008-07-13 23:50', '2008-07-14 00:20'] => 0.5}.each do |range, hours| |
|
83 |
|
|
84 |
define_method "test_hours_calculated_#{range[0]}_to_#{range[1]}" do |
|
85 |
|
|
86 |
#add default day if not specified |
|
87 |
range = range.map {|time| time['-'] ? time : '2008-07-13 ' + time} |
|
88 |
|
|
89 |
entry = TimeEntry.new successful_params.merge(:hours => nil, |
|
90 |
:start_time => range[0], :end_time => range[1]) |
|
91 |
entry.save! |
|
92 |
assert_in_delta hours, entry.hours, 0.0001 |
|
93 |
end |
|
94 |
end |
|
95 |
|
|
96 |
def assert_intersects(source, dest) |
|
97 |
intersecting = time_entries(source).find_intersecting_entries |
|
98 |
|
|
99 |
assert !intersecting.empty?, |
|
100 |
"there should be intersecting entries for #{source.inspect}" |
|
101 |
|
|
102 |
assert intersecting.map {|e| e.id}. |
|
103 |
include?(time_entries(dest).id), |
|
104 |
"#{source.inspect} should intersect with #{dest.inspect}" |
|
105 |
|
|
106 |
intersecting |
|
107 |
end |
|
108 |
|
|
109 |
def test_find_intersecting_entries_for_incomplete |
|
110 |
assert_intersects(:time_entry_in_progress, :intersecting_time_entry) |
|
111 |
end |
|
112 | ||
113 |
def test_find_intersecting_entries_for_complete_doesnt_find_itself |
|
114 |
intersecting = assert_intersects(:intersecting_time_entry, |
|
115 |
:time_entry_in_progress) |
|
116 | ||
117 |
assert !intersecting.map {|e| e.id}.include?( |
|
118 |
time_entries(:intersecting_time_entry)), 'time entry\'s ' + |
|
119 |
'intersecting entries shouldn\'t include itself' |
|
120 |
end |
|
121 | ||
122 |
def test_find_intersecting_entries_for_big |
|
123 |
assert_intersects(:big_intersecting_time_entry, :time_entry_in_progress) |
|
124 |
assert_intersects(:big_intersecting_time_entry, :intersecting_time_entry) |
|
125 |
end |
|
46 | 126 |
end |
vendor/plugins/test_utils/MIT-LICENSE (revision 0) | ||
---|---|---|
1 |
Copyright (C) 2008 Texuna Technologies |
|
2 |
|
|
3 |
Permission is hereby granted, free of charge, to any person obtaining |
|
4 |
a copy of this software and associated documentation files (the |
|
5 |
"Software"), to deal in the Software without restriction, including |
|
6 |
without limitation the rights to use, copy, modify, merge, publish, |
|
7 |
distribute, sublicense, and/or sell copies of the Software, and to |
|
8 |
permit persons to whom the Software is furnished to do so, subject to |
|
9 |
the following conditions: |
|
10 |
|
|
11 |
The above copyright notice and this permission notice shall be |
|
12 |
included in all copies or substantial portions of the Software. |
|
13 |
|
|
14 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
15 |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
16 |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
17 |
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|
18 |
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|
19 |
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|
20 |
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
21 |
|
vendor/plugins/test_utils/tasks/test_utils.rake (revision 0) | ||
---|---|---|
1 |
## |
|
2 |
# based on http://nubyonrails.com/articles/foscon-and-living-dangerously-with-rake |
|
3 |
# Run a single test (or group of tests started with given string) in Rails. |
|
4 |
# |
|
5 |
# rake blogs-list (or f_blogs-list) |
|
6 |
# => Runs test_list for BlogsController (functional test; use f_blogs-list to force it if unit test found) |
|
7 |
# |
|
8 |
# rake blog-create (or u_blog-create) |
|
9 |
# => Runs test_create for BlogTest (unit test; use u_blog-create to force it if functional test found)) |
|
10 |
|
|
11 |
#test file will be matched in the order of this array items |
|
12 |
TEST_TYPES = [ |
|
13 |
['u_', "unit/[file_name]_test.rb"], |
|
14 |
['f_', "functional/[file_name]_controller_test.rb"], |
|
15 |
['i_', "integration/[file_name]_test.rb"], |
|
16 |
['l_', "long/[file_name]_test.rb"], |
|
17 |
] |
|
18 |
|
|
19 |
rule "" do |t| |
|
20 |
all_flags = TEST_TYPES.map { |item| item[0] } |
|
21 |
if Regexp.new("(#{all_flags.join '|'}|)(.*)\\-([^.]+)$").match(t.name) |
|
22 |
flag = $1 |
|
23 |
file_name = $2 |
|
24 |
test_name = $3 |
|
25 |
|
|
26 |
path_getter = lambda { |type_info| type_info[1].gsub '[file_name]', file_name} |
|
27 |
file_path = nil |
|
28 |
TEST_TYPES.each do |type_info| |
|
29 |
my_file_path = path_getter.call(type_info) |
|
30 |
if flag == type_info[0] |
|
31 |
type_info[1].match /((.+)\/)/ |
|
32 |
type = $2 |
|
33 |
puts "forced #{type} test" |
|
34 |
file_path = my_file_path |
|
35 |
break |
|
36 |
end |
|
37 |
end |
|
38 |
|
|
39 |
if file_path && !File.exist?("test/#{file_path}") |
|
40 |
raise "No file found for #{file_path}" |
|
41 |
end |
|
42 |
|
|
43 |
if !file_path |
|
44 |
TEST_TYPES.each do |type_info| |
|
45 |
my_file_path = path_getter.call(type_info) |
|
46 |
if File.exist? "test/#{my_file_path}" |
|
47 |
puts "found #{my_file_path}" |
|
48 |
file_path = my_file_path |
|
49 |
break |
|
50 |
end |
|
51 |
end |
|
52 |
end |
|
53 |
|
|
54 |
if !file_path |
|
55 |
raise "No file found for #{file_name}" |
|
56 |
end |
|
57 |
|
|
58 |
begin |
|
59 |
sh "ruby -Ilib:test test/#{file_path} -n /^test_#{test_name}/" |
|
60 |
rescue Exception => e |
|
61 |
#no logger here, oops! |
|
62 |
#log.debug "error executing tests: #{e.inspect}" |
|
63 |
puts "error executing tests: #{e.inspect}" |
|
64 |
end |
|
65 |
end |
|
66 |
end |