Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First version

  • Loading branch information...
commit 558bfd0f5e8820f4cabc41d796507008a5298fc5 0 parents
@delaitre delaitre authored
5 .gitignore
@@ -0,0 +1,5 @@
+*.swp
+*.user
+*~
+~*
+
16 README.rdoc
@@ -0,0 +1,16 @@
+= time_tracker
+
+Time tracker is a redmine plugin to ease time tracking when working on an issue.
+The plugin allows to start/stop a timer on a per user basis.
+
+The time tracker is displayed in the 'account' menu bar.
+To start the time tracker, the user must have the 'log time' permission. Then, the time tracker will
+display a 'start' link in the menu when an issue is shown.
+If a tracker is running, the tracked issue id is displayed with the current spent time (updated
+every 5 secs). A 'stop' link is also available. When stopping the time tracker, the user is
+redirected to the standard timelog edit page with the 'hours' field filled with the timer value.
+This way, we rely on existing and well known behaviours.
+
+As mentionned before, the time tracker menu is updated periodically. This way, other browser
+tabs/windows will display the correct information.
+
44 app/controllers/time_trackers_controller.rb
@@ -0,0 +1,44 @@
+class TimeTrackersController < ApplicationController
+ unloadable
+
+ def start
+ if current.nil?
+ @issue = Issue.find(:first, :conditions => { :id => params[:issue_id] })
+
+ new_tracker = TimeTracker.new({ :issue_id => @issue.id })
+ if new_tracker.save
+ render_menu
+ else
+ flash[:error] = l(:start_time_tracker_error)
+ end
+ else
+ flash[:error] = l(:time_tracker_already_running_error)
+ end
+ end
+
+ def stop
+ @time_tracker = current
+ if @time_tracker.nil?
+ flash[:error] = l(:no_time_tracker_running_error)
+ redirect_to :back
+ else
+ issue_id = @time_tracker.issue_id
+ hours = @time_tracker.hours_spent
+ @time_tracker.destroy
+
+ redirect_to :controller => 'timelog', :action => 'edit', :issue_id => issue_id, :time_entry => { :hours => hours }
+ end
+ end
+
+ def render_menu
+ @project = Project.find(:first, :conditions => { :id => params[:project_id] })
+ @issue = Issue.find(:first, :conditions => { :id => params[:issue_id] })
+ render :partial => 'embed_menu'
+ end
+
+ protected
+
+ def current
+ TimeTracker.find(:first, :conditions => { :user_id => User.current.id })
+ end
+end
6 app/helpers/application_helper.rb
@@ -0,0 +1,6 @@
+module ApplicationHelper
+ def time_tracker_for(user)
+ TimeTracker.find(:first, :conditions => { :user_id => user.id })
+ end
+end
+
22 app/models/time_tracker.rb
@@ -0,0 +1,22 @@
+class TimeTracker < ActiveRecord::Base
+ belongs_to :user
+ has_one :issue
+
+ validates_presence_of :issue_id
+
+ def initialize(arguments = nil)
+ super(arguments)
+ self.user_id = User.current.id
+ self.started_on = Time.now
+ end
+
+ def hours_spent
+ ((Time.now.to_i - started_on.to_i) / 3600.0).to_f
+ end
+
+ def time_spent_to_s
+ hours, minutes = Date.day_fraction_to_time(DateTime.now - started_on.to_datetime)
+ hours.to_s + l(:time_tracker_hour_sym) + minutes.to_s.rjust(2, '0')
+ end
+end
+
20 app/views/time_trackers/_embed_menu.rhtml
@@ -0,0 +1,20 @@
+<% time_tracker = time_tracker_for(User.current) %>
+<% if !time_tracker.nil? %>
+ <%= link_to '#' + time_tracker.issue_id.to_s,
+ { :controller => 'issues', :action => 'show', :id => time_tracker.issue_id },
+ { :class => 'icon icon-clock' }
+ %>
+ (<%= time_tracker.time_spent_to_s %> /
+ <%= link_to l(:stop_time_tracker), { :controller => 'time_trackers', :action => 'stop' } %>)
+<% elsif !@project.nil? and User.current.allowed_to?(:log_time, @project) %>
+ <% if !@issue.nil? %>
+ <%= link_to_remote l(:start_time_tracker) + ' #' + @issue.id.to_s,
+ :url => { :controller => 'time_trackers', :action => 'start', :issue_id => @issue.id },
+ :html => { :class => 'icon icon-time-add' },
+ :update => 'time-tracker-menu'
+ %>
+ <% else %>
+ <span class='icon icon-time'><%= l(:time_tracker_not_running) %></span>
+ <% end %>
+<% end %>
+
29 app/views/time_trackers/_update_menu.rhtml
@@ -0,0 +1,29 @@
+<!--
+This script replaces the <a id="time-tracker-menu">...</a> element by a span with the same id.
+This is needed as we want several links for our time tracker menu item.
+The span is populated with current time tracker state.
+(no AJAX request here, this allows to have the time tracker immediately visible)
+-->
+<%= javascript_tag "document.observe('dom:loaded', function () {
+ $('time-tracker-menu').replace(
+ '<span id=\"time-tracker-menu\">#{escape_javascript ((render :partial => 'time_trackers/embed_menu').delete "\n").strip}</span>');
+ });"
+%>
+
+<!--
+This script defines the updateTimeTrackerMenu() function which will update
+the time tracker menu item using an AJAX request to retrieve the data
+-->
+<%= javascript_tag "function updateTimeTrackerMenu() {
+ #{remote_function(
+ :url => { :controller => 'time_trackers', :action => 'render_menu', :project_id => (@project.nil? ? nil : @project.id), :issue_id => (@issue.nil? ? nil : @issue.id) },
+ :before => "Ajax.activeRequestCount--",
+ :complete => "updateElementIfChanged('time-tracker-menu', request.responseText);")
+ };}"
+%>
+
+<!--
+This script periodically updates the time tracker menu item to reflect any changes in the tracking state
+-->
+<%= javascript_tag "new PeriodicalExecuter(updateTimeTrackerMenu, 5);" %>
+
BIN  assets/images/clock.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 assets/javascripts/time_tracker.js
@@ -0,0 +1,6 @@
+
+function updateElementIfChanged(id, newContent) {
+ el = $(id);
+ if (el.innerHTML != newContent) { el.update(newContent); }
+}
+
6 assets/stylesheets/time_tracker.css
@@ -0,0 +1,6 @@
+
+.icon-clock { background-image: url(../images/clock.gif); }
+
+#time-tracker-menu { margin-right: 8px; }
+#time-tracker-menu a { margin-right: 0px; }
+
10 config/locales/en.yml
@@ -0,0 +1,10 @@
+# English strings go here for Rails i18n
+en:
+ start_time_tracker: "start"
+ stop_time_tracker: "stop"
+ time_tracker_not_running: "Not running"
+ start_time_tracker_error: "Fail to start the time tracker"
+ time_tracker_already_running_error: "A time tracker is already running"
+ no_time_tracker_running_error: "No time tracker is running"
+ time_tracker_hour_sym: ":"
+
10 config/locales/fr.yml
@@ -0,0 +1,10 @@
+# French strings go here for Rails i18n
+fr:
+ start_time_tracker: "démarrer"
+ stop_time_tracker: "arrêter"
+ time_tracker_not_running: "Arrêté"
+ start_time_tracker_error: "Le traqueur de temps n'a pu être démarré"
+ time_tracker_already_running_error: "Un traqueur de temps est déjà démarré"
+ no_time_tracker_running_error: "Aucun traqueur de temps n'est démarré"
+ time_tracker_hour_sym: ":"
+
13 db/migrate/001_create_time_trackers.rb
@@ -0,0 +1,13 @@
+class CreateTimeTrackers < ActiveRecord::Migration
+ def self.up
+ create_table :time_trackers do |t|
+ t.column :user_id, :integer
+ t.column :issue_id, :integer
+ t.column :started_on, :datetime
+ end
+ end
+
+ def self.down
+ drop_table :time_trackers
+ end
+end
21 init.rb
@@ -0,0 +1,21 @@
+require 'redmine'
+
+require_dependency 'time_tracker_hooks'
+
+Redmine::Plugin.register :redmine_time_tracker do
+ name 'Redmine Time Tracker plugin'
+ author 'Jérémie Delaitre'
+ description 'This is a plugin to track time in Redmine'
+ version '0.0.1'
+
+ requires_redmine :version_or_higher => '0.9.0'
+
+ menu :account_menu, :time_tracker_menu, '',
+ {
+ :caption => '',
+ :html => { :id => 'time-tracker-menu' },
+ :first => true,
+ :param => :project_id
+ }
+end
+
11 lib/time_tracker_hooks.rb
@@ -0,0 +1,11 @@
+# This class hooks into Redmine's View Listeners in order to add content to the page
+class TimeTrackerHooks < Redmine::Hook::ViewListener
+ render_on :view_layouts_base_body_bottom, :partial => 'time_trackers/update_menu'
+
+ def view_layouts_base_html_head(context = {})
+ css = stylesheet_link_tag 'time_tracker.css', :plugin => 'redmine_time_tracker'
+ js = javascript_include_tag 'time_tracker.js', :plugin => 'redmine_time_tracker'
+ css + js
+ end
+end
+
11 test/fixtures/time_trackers.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+ id: 1
+ user_id: 1
+ issue_id: 1
+ started_on: 2010-02-25 11:47:03
+two:
+ id: 2
+ user_id: 1
+ issue_id: 1
+ started_on: 2010-02-25 11:47:03
8 test/functional/time_trackers_controller_test.rb
@@ -0,0 +1,8 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class TimeTrackersControllerTest < ActionController::TestCase
+ # Replace this with your real tests.
+ def test_truth
+ assert true
+ end
+end
5 test/test_helper.rb
@@ -0,0 +1,5 @@
+# Load the normal Rails helper
+require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
+
+# Ensure that we are using the temporary fixture path
+Engines::Testing.set_fixture_path
10 test/unit/time_tracker_test.rb
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class TimeTrackerTest < ActiveSupport::TestCase
+ fixtures :time_trackers
+
+ # Replace this with your real tests.
+ def test_truth
+ assert true
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.