mirror of
https://github.com/rickbarrette/stopwatch.git
synced 2026-04-02 09:51:57 -04:00
initial commit
This commit is contained in:
51
app/controllers/stopwatch_timers_controller.rb
Normal file
51
app/controllers/stopwatch_timers_controller.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
class StopwatchTimersController < ApplicationController
|
||||
helper :timelog
|
||||
|
||||
before_action :require_login
|
||||
before_action :find_optional_data, only: %i(new create)
|
||||
|
||||
def new
|
||||
@time_entry = new_time_entry
|
||||
respond_to :js
|
||||
end
|
||||
|
||||
def create
|
||||
@time_entry = new_time_entry
|
||||
@time_entry.safe_attributes = params[:time_entry]
|
||||
@result = Stopwatch::StartTimer.new(@time_entry).call
|
||||
unless @result.success?
|
||||
if @result.error == :unauthorized
|
||||
render_403
|
||||
else
|
||||
render_error status: 422, message: "could not start timer: #{@result.error}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
|
||||
end
|
||||
|
||||
def update
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_time_entry
|
||||
TimeEntry.new(project: @project, issue: @issue,
|
||||
user: User.current, spent_on: User.current.today)
|
||||
end
|
||||
|
||||
def find_optional_data
|
||||
if params[:issue_id].present?
|
||||
@issue = Issue.find(params[:issue_id])
|
||||
@project = @issue.project
|
||||
elsif params[:project_id].present?
|
||||
@project = Project.find(params[:project_id])
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
end
|
||||
11
app/views/stopwatch_timers/_edit.html.erb
Normal file
11
app/views/stopwatch_timers/_edit.html.erb
Normal file
@@ -0,0 +1,11 @@
|
||||
<h3 class="title"><%= l(:label_spent_time) %></h3>
|
||||
|
||||
<%= labelled_form_for @time_entry, url: stopwatch_path, remote: true do |f| %>
|
||||
<% @time_entry.hours ||= 0 %>
|
||||
<%= render partial: 'timelog/form', locals: { f: f } %>
|
||||
|
||||
<p class="buttons">
|
||||
<%= submit_tag l(:button_create) %>
|
||||
<%= submit_tag l(:button_cancel), name: nil, onclick: "hideModal(this);", type: 'button' %>
|
||||
</p>
|
||||
<% end %>
|
||||
12
app/views/stopwatch_timers/_new.html.erb
Normal file
12
app/views/stopwatch_timers/_new.html.erb
Normal file
@@ -0,0 +1,12 @@
|
||||
<h3 class="title"><%= l(:button_log_time) %></h3>
|
||||
|
||||
<%= labelled_form_for @time_entry, url: stopwatch_timer_path, method: :post, remote: true do |f| %>
|
||||
<% @time_entry.hours ||= 0 %>
|
||||
<%= render partial: 'timelog/form', locals: { f: f } %>
|
||||
|
||||
<p class="buttons">
|
||||
<%= submit_tag l(:button_create) %>
|
||||
<%= submit_tag l(:button_cancel), name: nil, onclick: "hideModal(this);", type: 'button' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
2
app/views/stopwatch_timers/new.js.erb
Normal file
2
app/views/stopwatch_timers/new.js.erb
Normal file
@@ -0,0 +1,2 @@
|
||||
$('#ajax-modal').html('<%= j render partial: 'stopwatch_timers/new' %>');
|
||||
showModal('ajax-modal', '600px');
|
||||
1
config/locales/en.yml
Normal file
1
config/locales/en.yml
Normal file
@@ -0,0 +1 @@
|
||||
en:
|
||||
2
config/routes.rb
Normal file
2
config/routes.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
resource :stopwatch_timer, only: %i(new create edit update)
|
||||
|
||||
25
init.rb
Normal file
25
init.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
require 'redmine'
|
||||
require_dependency 'stopwatch'
|
||||
require 'stopwatch/hooks'
|
||||
|
||||
Redmine::Plugin.register :stopwatch do
|
||||
name 'Redmine Stopwatch Plugin'
|
||||
author 'Jens Krämer'
|
||||
author_url 'https://jkraemer.net/'
|
||||
description 'Adds start/stop timer functionality'
|
||||
version '0.1.0'
|
||||
|
||||
requires_redmine version_or_higher: '3.4.0'
|
||||
|
||||
menu :account_menu, :stopwatch,
|
||||
:new_stopwatch_timer_path,
|
||||
caption: :button_log_time,
|
||||
html: { method: :get, id: 'stopwatch-toggle', 'data-remote': true },
|
||||
before: :my_account,
|
||||
if: ->(*_){ User.current.logged? }
|
||||
end
|
||||
|
||||
Rails.configuration.to_prepare do
|
||||
Stopwatch::UserPatch.apply
|
||||
end
|
||||
|
||||
3
lib/stopwatch.rb
Normal file
3
lib/stopwatch.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
module Stopwatch
|
||||
|
||||
end
|
||||
0
lib/stopwatch/hooks.rb
Normal file
0
lib/stopwatch/hooks.rb
Normal file
44
lib/stopwatch/start_timer.rb
Normal file
44
lib/stopwatch/start_timer.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
module Stopwatch
|
||||
class StartTimer
|
||||
|
||||
Result = ImmutableStruct.new(:success?, :error)
|
||||
|
||||
def initialize(time_entry, user: User.current)
|
||||
@time_entry = time_entry
|
||||
@user = user
|
||||
end
|
||||
|
||||
def call
|
||||
if @time_entry.project && @user.allowed_to?(:log_time, @time_entry.project)
|
||||
return Result.new(error: :unauthorized)
|
||||
end
|
||||
|
||||
stop_existing_timer
|
||||
|
||||
@time_entry.hours = 0 if @time_entry.hours.nil?
|
||||
|
||||
if @time_entry.save
|
||||
start_new_timer
|
||||
return Result.new(success: true)
|
||||
else
|
||||
Rails.logger.error("could not save time entry: \n#{@time_entry.errors.inspect}")
|
||||
return Result.new(error: :invalid)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def start_new_timer
|
||||
timer = Timer.new @user
|
||||
timer.start @time_entry
|
||||
end
|
||||
|
||||
def stop_existing_timer
|
||||
timer = Timer.new @user
|
||||
if timer.running?
|
||||
timer.stop
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
51
lib/stopwatch/timer.rb
Normal file
51
lib/stopwatch/timer.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
require 'json'
|
||||
|
||||
module Stopwatch
|
||||
class Timer
|
||||
attr_reader :user
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
end
|
||||
|
||||
def running?
|
||||
data[:started_at].present?
|
||||
end
|
||||
|
||||
def start(time_entry = nil)
|
||||
fail "timer is already running" if running?
|
||||
data[:started_at] = Time.now.to_i
|
||||
data[:time_entry_id] = time_entry.id if time_entry
|
||||
save
|
||||
end
|
||||
|
||||
def stop
|
||||
if hours = runtime_hours and
|
||||
time_entry = TimeEntry.find_by_id(data[:time_entry_id])
|
||||
|
||||
time_entry.update_column :hours, time_entry.hours + hours
|
||||
end
|
||||
data[:started_at] = nil
|
||||
save
|
||||
end
|
||||
|
||||
def save
|
||||
user.pref[:current_timer] = data.to_json
|
||||
user.pref.save
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def data
|
||||
@data ||= (t = user.pref[:current_timer]) ? JSON.parse(t).symbolize_keys : {}
|
||||
end
|
||||
|
||||
def runtime_hours
|
||||
if start = data[:started_at]
|
||||
runtime = Time.now.to_i - start
|
||||
return runtime.to_f / 1.hour
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
11
lib/stopwatch/user_patch.rb
Normal file
11
lib/stopwatch/user_patch.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
module Stopwatch
|
||||
module UserPatch
|
||||
def self.apply
|
||||
User.prepend self unless User < self
|
||||
end
|
||||
|
||||
def timer_running?
|
||||
Stopwatch::Timer.new(self).running?
|
||||
end
|
||||
end
|
||||
end
|
||||
5
test/test_helper.rb
Normal file
5
test/test_helper.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
|
||||
|
||||
class ActiveSupport::TestCase
|
||||
|
||||
end
|
||||
40
test/unit/start_timer_test.rb
Normal file
40
test/unit/start_timer_test.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
require_relative '../test_helper'
|
||||
|
||||
class StartTimerTest < ActiveSupport::TestCase
|
||||
fixtures :users, :user_preferences, :time_entries, :projects,
|
||||
:roles, :member_roles, :members, :enumerations
|
||||
|
||||
setup do
|
||||
@user = User.find 1
|
||||
@time_entry = TimeEntry.last
|
||||
end
|
||||
|
||||
test "should start timer" do
|
||||
refute User.find(@user.id).timer_running?
|
||||
assert r = Stopwatch::StartTimer.new(@time_entry, user: @user).call
|
||||
assert r.success?, r.inspect
|
||||
assert User.find(@user.id).timer_running?
|
||||
end
|
||||
|
||||
test "should stop and save existing timer" do
|
||||
hours = @time_entry.hours
|
||||
r = Stopwatch::StartTimer.new(@time_entry, user: @user).call
|
||||
assert r.success?
|
||||
t = Stopwatch::Timer.new(@user)
|
||||
data = t.send(:data)
|
||||
data[:started_at] = 1.hour.ago.to_i
|
||||
t.save
|
||||
|
||||
@time_entry.reload
|
||||
assert_equal hours, @time_entry.hours
|
||||
another = TimeEntry.new(@time_entry.attributes)
|
||||
another.user = @user
|
||||
r = Stopwatch::StartTimer.new(another, user: @user).call
|
||||
assert r.success?
|
||||
@time_entry.reload
|
||||
assert_equal hours+1, @time_entry.hours
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
52
test/unit/timer_test.rb
Normal file
52
test/unit/timer_test.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
require_relative '../test_helper'
|
||||
|
||||
class TimerTest < ActiveSupport::TestCase
|
||||
fixtures :users, :user_preferences, :time_entries
|
||||
|
||||
setup do
|
||||
@user = User.find 1
|
||||
@timer = Stopwatch::Timer.new @user
|
||||
@data = @timer.send :data
|
||||
@time_entry = TimeEntry.last
|
||||
end
|
||||
|
||||
test "should know wether its running" do
|
||||
refute @timer.running?
|
||||
@data[:started_at] = 5.minutes.ago
|
||||
assert @timer.running?
|
||||
end
|
||||
|
||||
test "should start timer" do
|
||||
assert_nil @data[:started_at]
|
||||
@timer.start @time_entry
|
||||
assert @data[:started_at].present?
|
||||
assert_equal @time_entry.id, @data[:time_entry_id]
|
||||
end
|
||||
|
||||
test "should stop timer and update time entry" do
|
||||
hours = @time_entry.hours
|
||||
@timer.start @time_entry
|
||||
@data[:started_at] = 1.hour.ago.to_i
|
||||
@timer.stop
|
||||
|
||||
@time_entry.reload
|
||||
assert_equal hours + 1, @time_entry.hours
|
||||
end
|
||||
|
||||
test "should save and restore" do
|
||||
hours = @time_entry.hours
|
||||
@timer.start @time_entry
|
||||
@data[:started_at] = 1.hour.ago.to_i
|
||||
@timer.save
|
||||
|
||||
timer = Stopwatch::Timer.new User.find(@user.id)
|
||||
assert timer.running?
|
||||
timer.stop
|
||||
|
||||
@time_entry.reload
|
||||
assert_equal hours + 1, @time_entry.hours
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
21
test/unit/user_test.rb
Normal file
21
test/unit/user_test.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
require_relative '../test_helper'
|
||||
|
||||
class UserTest < ActiveSupport::TestCase
|
||||
fixtures :users, :user_preferences
|
||||
|
||||
setup do
|
||||
@user = User.find 1
|
||||
end
|
||||
|
||||
test "should get inactive timer state" do
|
||||
refute @user.timer_running?
|
||||
end
|
||||
|
||||
test "should start timer and get running state" do
|
||||
t = Stopwatch::Timer.new @user
|
||||
t.start
|
||||
t.save
|
||||
assert @user.timer_running?
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user