Skip to content

Commit

Permalink
Merge branch 'feature/start-stop-ticket'
Browse files Browse the repository at this point in the history
  • Loading branch information
jkraemer committed Oct 6, 2021
2 parents 88c1748 + 78e47f5 commit 8e572e7
Show file tree
Hide file tree
Showing 28 changed files with 562 additions and 38 deletions.
12 changes: 12 additions & 0 deletions app/controllers/stopwatch_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# base class for stopwatch controllers
class StopwatchController < ApplicationController
helper :timelog, :custom_fields

before_action :require_login

private

def authorize_edit_time
@time_entry.editable_by?(User.current) or deny_access
end
end
48 changes: 48 additions & 0 deletions app/controllers/stopwatch_issue_timers_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
class StopwatchIssueTimersController < StopwatchController
before_action :find_issue
before_action :authorize_log_time

def start
t = Stopwatch::IssueTimer.new(issue: @issue)
if t.running?
head 422; return
end

time_entry = User.current.todays_time_entry_for(@issue)
if time_entry.new_record?
sys_default_activity = time_entry.activity
time_entry.activity = Stopwatch.default_activity_for time_entry
end

r = Stopwatch::StartTimer.new(time_entry).call
if r.success?
@started_time_entry = time_entry
render status: :created
else
@time_entry = time_entry
# in case the setting is 'always ask', still preset the form to the global default
@time_entry.activity ||= sys_default_activity
@time_entry.errors.clear
render status: :ok
end
end

def stop
r = Stopwatch::StopTimer.new.call
unless r.success?
logger.error "unable to stop timer"
head 422; return
end
end

private

def authorize_log_time
User.current.allowed_to?(:log_time, @project) or deny_access
end

def find_issue
@issue = Issue.find params[:issue_id]
@project = @issue.project
end
end
20 changes: 15 additions & 5 deletions app/controllers/stopwatch_timers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
# - same for project, unless we are in a project context
# - focus first field that needs an action, depending on above
#
class StopwatchTimersController < ApplicationController
helper :timelog, :custom_fields
class StopwatchTimersController < StopwatchController

before_action :require_login
before_action :find_optional_data, only: %i(new create)
before_action :authorize_log_time, only: %i(new create start stop current)
before_action :find_time_entry, only: %i(edit update start stop)
Expand Down Expand Up @@ -62,7 +60,9 @@ def start

def stop
r = Stopwatch::StopTimer.new.call
unless r.success?
if r.success?
@stopped_time_entry = @time_entry
else
logger.error "unable to stop timer"
end
new unless params[:context]
Expand All @@ -73,6 +73,17 @@ def current
render json: @timer.to_json
end

def update_form
if id = params[:time_entry_id].presence
@time_entry = TimeEntry.visible.find id
else
@time_entry = TimeEntry.new
end
@time_entry.safe_attributes = params[:time_entry]
rescue ActiveRecord::RecordNotFound
head 404
end

private

def find_timer
Expand All @@ -82,7 +93,6 @@ def find_timer

def find_time_entry
@time_entry = time_entries.find params[:id]

end

def load_todays_entries
Expand Down
4 changes: 4 additions & 0 deletions app/views/stopwatch/_settings.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<p>
<label for="settings_default_activity"><%= t '.label_default_activity' %> </label>
<%= select_tag 'settings[default_activity]', options_from_collection_for_select( [['always_ask', t('.label_always_ask')], ['system', t('.label_system')]] + TimeEntryActivity.system.active.to_a.pluck(:id, :name), :first, :last, Stopwatch.settings['default_activity'] ) %>
</p>
6 changes: 5 additions & 1 deletion app/views/stopwatch/hooks/_layouts_base_body_bottom.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<%= javascript_tag do %>
window.stopwatch = window.initStopwatch({
currentTimerUrl: '<%= j current_stopwatch_timers_url %>',
hourFormat: '<%= j format_hours 0.0 %>'
hourFormat: '<%= j format_hours 0.0 %>',
locales: {
startTimer: '<%= j l :label_stopwatch_start %>',
stopTimer: '<%= j l :label_stopwatch_stop %>'
},
});
<% if User.current.logged? %>
window.stopwatch.highlightRunningTimer(
Expand Down
20 changes: 20 additions & 0 deletions app/views/stopwatch_issue_timers/_new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<h3 class="title"><%= l(:button_log_time) %> - <%= format_date User.current.today %> - <%= "#{@issue.tracker} #{@issue.id}: #{truncate @issue.subject}" %></h3>

<%= labelled_form_for @time_entry, url: stopwatch_timers_path, method: :post, remote: true do |f| %>
<% @time_entry.hours ||= 0 %>
<%= f.hidden_field :issue_id, value: @issue.id %>

<fieldset class="box tabular">
<legend><%= t 'stopwatch_timers.entry_form.legend_new' %></legend>
<p><%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %></p>
<p><%= f.text_field :comments, :size => 100, :maxlength => 1024, :required => Setting.timelog_required_fields.include?('comments') %></p>
<% @time_entry.custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :time_entry, value %></p>
<% end %>
</fieldset>

<p class="buttons">
<%= submit_tag l(:button_create) %>
</p>
<% end %>

9 changes: 9 additions & 0 deletions app/views/stopwatch_issue_timers/start.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<% if @started_time_entry %>
contextMenuHide();
window.stopwatch.timerStarted(
<%= raw Stopwatch::Timer.new(User.current).to_json %>
);
<% else %>
$('#ajax-modal').html('<%= j render partial: 'new' %>');
showModal('ajax-modal', '700px');
<% end %>
6 changes: 6 additions & 0 deletions app/views/stopwatch_issue_timers/stop.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
contextMenuHide();
window.stopwatch.updateStartStopLink(
'#stopwatch_stop_timer_<%= @issue.id %>',
'<%= j Stopwatch::IssueLinks.new(@issue).start_timer %>'
);
window.stopwatch.timerStopped();
31 changes: 17 additions & 14 deletions app/views/stopwatch_timers/_entry_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

<fieldset class="box tabular">
<legend><%= t @time_entry.new_record? ? '.legend_new' : '.legend_edit' %></legend>
<p><%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %></p>

<p><%= f.select :project_id, project_tree_options_for_select(Project.allowed_to(:log_time).to_a, :selected => @time_entry.project, :include_blank => true), :required => true %></p>

<p>
<%= f.text_field :issue_id, :size => 6, :required => Setting.timelog_required_fields.include?('issue_id') %>
<span id="time_entry_issue">
<p><label for="stopwatch_time_entry_project_id"><%= l :field_project %><span class="required"> *</span></label><%= f.select :project_id, project_tree_options_for_select(Project.allowed_to(:log_time).to_a, :selected => @time_entry.project, :include_blank => true), { no_label: true }, id: 'stopwatch_time_entry_project_id' %></p>
<p><label for="stopwatch_time_entry_issue_id"><%= l :field_issue %>
<% if Setting.timelog_required_fields.include?('issue_id') %>
<span class="required"> *</span>
<% end %>
</label>
<%= f.text_field :issue_id, :size => 6, no_label: true, id: 'stopwatch_time_entry_issue_id' %>
<span id="stopwatch_time_entry_issue">
<%= link_to_issue(@time_entry.issue) if @time_entry.issue.try(:visible?) %>
</span>
</p>
<p><label for="stopwatch_time_entry_activity_id"><%= l :field_activity %><span class="required"> *</span></label><%= f.select :activity_id, activity_collection_for_select_options(@time_entry), { no_label: true }, id: 'stopwatch_time_entry_activity_id' %></p>
<!--
<p><%= f.date_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p>
-->
Expand All @@ -25,19 +28,19 @@

<%= javascript_tag do %>
$(document).ready(function(){
$('#time_entry_project_id').change(function(){
$('#time_entry_issue_id').val('');
$('#stopwatch_time_entry_project_id').change(function(){
$('#stopwatch_time_entry_issue_id').val('');
});
$('#time_entry_project_id, #time_entry_issue_id').change(function(){
$('#stopwatch_time_entry_project_id, #stopwatch_time_entry_issue_id').change(function(){
$.ajax({
url: '<%= escape_javascript(@time_entry.new_record? ? new_time_entry_path(format: 'js') : edit_time_entry_path(@time_entry, format: 'js')) %>',
url: '<%= j update_form_stopwatch_timers_path(time_entry_id: @time_entry.id, format: 'js') %>',
type: 'post',
data: $(this).closest('form').serialize()
});
});
});

observeAutocompleteField('time_entry_issue_id',
observeAutocompleteField('stopwatch_time_entry_issue_id',
function(request, callback) {
var url = '<%= j auto_complete_issues_path %>';
var data = {
Expand All @@ -47,7 +50,7 @@
<% if @time_entry.new_record? && @project %>
project_id = '<%= @project.id %>';
<% else %>
project_id = $('#time_entry_project_id').val();
project_id = $('#stopwatch_time_entry_project_id').val();
<% end %>
if(project_id){
data['project_id'] = project_id;
Expand All @@ -65,8 +68,8 @@
},
{
select: function(event, ui) {
$('#time_entry_issue').text('');
$('#time_entry_issue_id').val(ui.item.value).change();
$('#stopwatch_time_entry_issue').text('');
$('#stopwatch_time_entry_issue_id').val(ui.item.value).change();
}
}
);
Expand Down
6 changes: 4 additions & 2 deletions app/views/stopwatch_timers/_new.html.erb
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<h3 class="title"><%= l(:button_log_time) %> - <%= format_date User.current.today %></h3>

<%= render partial: 'entries_list', locals: { entries: @entries } %>
<% if @entries %>
<%= render partial: 'stopwatch_timers/entries_list', locals: { entries: @entries } %>
<% end %>

<%= labelled_form_for @time_entry, url: stopwatch_timers_path, method: :post, remote: true do |f| %>
<% @time_entry.hours ||= 0 %>
<%= render partial: 'entry_form', locals: { f: f } %>
<%= render partial: 'stopwatch_timers/entry_form', locals: { f: f } %>

<p class="buttons">
<%= submit_tag l(:button_create) %>
Expand Down
3 changes: 2 additions & 1 deletion app/views/stopwatch_timers/create.js.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
hideModal();
window.stopwatch.highlightRunningTimer(
window.stopwatch.timerStarted(
<%= raw Stopwatch::Timer.new(User.current).to_json %>
);

7 changes: 4 additions & 3 deletions app/views/stopwatch_timers/start.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
<% end %>

<% if @started_time_entry %>
window.stopwatch.timerStarted('<%= @started_time_entry.id %>',
'<%= j format_hours @started_time_entry.hours %>');
window.stopwatch.timerStarted(
<%= raw Stopwatch::Timer.new(User.current).to_json %>
);
<% else %>
window.stopwatch.timerStopped();
window.stopwatch.timerStopped();
<% end %>

5 changes: 5 additions & 0 deletions app/views/stopwatch_timers/update_form.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
$('#stopwatch_time_entry_activity_id').html('<%= escape_javascript options_for_select(activity_collection_for_select_options(@time_entry), @time_entry.activity_id) %>');
$('#stopwatch_time_entry_issue').html('<%= escape_javascript link_to_issue(@time_entry.issue) if @time_entry.issue.try(:visible?) %>');
$('#stopwatch_time_entry_project_id').html('<%= escape_javascript project_tree_options_for_select(Project.allowed_to(:log_time).to_a, :selected => @time_entry.project, :include_blank => true) %>');


42 changes: 37 additions & 5 deletions assets/javascripts/stopwatch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
window.initStopwatch = function(config){
var currentTimerUrl = config.currentTimerUrl;
var hourFormat = config.hourFormat;
var locales = config.locales;

var hoursRe = hourFormat.replace(/0+/g, '\\d+').replace(/\./g, '\\.');
var titleRegexp = new RegExp('^(' + hoursRe + ' - )?(.*)$');
Expand Down Expand Up @@ -68,12 +69,43 @@ window.initStopwatch = function(config){
highlightRunningTimer: highlightRunningTimer,
timerStopped: function(){
highlightRunningTimer();
// fix up any issue timer start/stop links in the UI
// no running timer -> all links will start a timer
$('a.stopwatch_issue_timer').each(function(){
var a = $(this);
a.attr('href', a.attr('href').replace(/stop$/, 'start'));
a.text(locales.startTimer);
});
},
timerStarted: function(data){
highlightRunningTimer(data);
// {
// running: true,
// time_entry_id: entryId,
// time_spent: spentTime
// });
// fix up any issue timer start/stop links in the UI
// all links will start a timer, except the one for the current issue,
// which has to be turned into a stop link.
if(data.running) {
$('a.stopwatch_issue_timer').each(function(){
var a = $(this);
var href = a.attr('href');
if(data.issue_id) {
if(a.data('issueId') == data.issue_id) {
a.attr('href', href.replace(/start$/, 'stop'));
a.text(locales.stopTimer);
} else {
a.attr('href', href.replace(/stop$/, 'start'));
a.text(locales.startTimer);
}
}
});
}
},
timerStarted: function(entryId, spentTime){
highlightRunningTimer({
running: true,
time_entry_id: entryId,
time_spent: spentTime
updateStartStopLink: function(id, replacement){
$(id).replaceWith(function(){
return $(replacement, { html: $(this).html() });
});
},
setProjectId: function(projectId){
Expand Down
5 changes: 5 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
en:
label_stopwatch_start: Start tracking
label_stopwatch_stop: Stop tracking
stopwatch:
settings:
label_always_ask: 'Always ask'
label_default_activity: 'Default activity for "Start tracking"'
label_system: 'Use system default'
stopwatch_timers:
entries_list:
button_stop: Stop
Expand Down
6 changes: 6 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
resources :stopwatch_timers, only: %i(new create edit update) do
collection do
get :current
post :update_form
end
member do
put :start
put :stop
end
end

scope 'issues/:issue_id' do
post 'timer/start', to: 'stopwatch_issue_timers#start', as: :start_issue_timer
post 'timer/stop', to: 'stopwatch_issue_timers#stop', as: :stop_issue_timer
end

10 changes: 8 additions & 2 deletions init.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
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 "Start/stop timer and quick access to today's time bookings for Redmine"
version '0.1.0'
version '0.2.0'

requires_redmine version_or_higher: '3.4.0'
settings default: {
'default_activity' => 'always_ask',
}, partial: 'stopwatch/settings'

menu :account_menu, :stopwatch,
:new_stopwatch_timer_path,
Expand All @@ -18,7 +22,9 @@
end

Rails.configuration.to_prepare do
Stopwatch::UserPatch.apply
Stopwatch::ContextMenusControllerPatch.apply
Stopwatch::IssuesControllerPatch.apply
Stopwatch::TimeEntryPatch.apply
Stopwatch::UserPatch.apply
end

Loading

0 comments on commit 8e572e7

Please sign in to comment.