15 Commits

Author SHA1 Message Date
Jens Kraemer
8e34eb627c fixes various UI issues 2025-06-24 15:48:03 +08:00
Jens Kraemer
3c709b1329 v1.1 for Redmine 6 2025-06-24 15:26:21 +08:00
Jens Kraemer
776c0cd823 switch to svg icons 2025-06-07 11:41:49 +08:00
Jens Krämer
e40840b974 Update README.md 2023-11-03 15:19:40 +08:00
Jens Kraemer
efc8c6c620 require_relative confuses the CI action 2023-11-03 15:17:22 +08:00
Jens Kraemer
33e8d85f8f remove travis leftovers 2023-11-03 15:04:49 +08:00
Jens Kraemer
7f0581aff2 travis -> GH actions 2023-11-03 14:41:58 +08:00
Jens Kraemer
c496fac5e2 Redmine 5 2023-11-03 14:41:58 +08:00
Jens Kraemer
45d15917b3 update travis badge and link 2021-10-06 13:11:19 +08:00
Jens Kraemer
fd7a5375e2 travis: do not attempt to run 4.1 with ruby 2.7 2021-10-06 13:02:42 +08:00
Jens Kraemer
8db0686e3a travis 2021-10-06 12:24:39 +08:00
Jens Kraemer
e435d5f350 travis 2021-10-06 12:11:06 +08:00
Jens Kraemer
8e572e79b5 Merge branch 'feature/start-stop-ticket' 2021-10-06 10:58:37 +08:00
Jens Kraemer
88c1748703 travis 2021-10-05 12:10:13 +08:00
Jens Kraemer
84a49f1d1c test feature branch, update ruby versions 2021-10-05 11:40:20 +08:00
24 changed files with 91 additions and 188 deletions

32
.github/workflows/redmine.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Test with Redmine
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
redmine:
- '5.0'
- '5.1'
ruby:
- '3.0'
- '3.1'
database:
- postgresql
- mysql
steps:
- uses: eXolnet/action-redmine-plugin@v1
with:
plugin_name: stopwatch
redmine_version: ${{ matrix.redmine }}
ruby_version: ${{ matrix.ruby }}
database: ${{ matrix.database }}

View File

@@ -1,33 +0,0 @@
sudo: false
language: ruby
rvm:
- 2.4.10
- 2.5.8
- 2.6.6
branches:
only:
- master
addons:
postgresql: "9.6"
env:
- REDMINE_VER=4.0-stable
- REDMINE_VER=4.1-stable
- REDMINE_VER=master
install: "echo skip bundle install"
before_script:
- psql -c 'create database travis_ci_test;' -U postgres
script:
- export TESTSPACE=`pwd`/testspace
- export NAME_OF_PLUGIN=stopwatch
- export PATH_TO_PLUGIN=`pwd`
- export PATH_TO_REDMINE=$TESTSPACE/redmine
- mkdir $TESTSPACE
- cp test/support/* $TESTSPACE/
- bash -x ./travis.sh

View File

@@ -1,4 +1,4 @@
# Stopwatch Plugin for Redmine [![Build Status](https://travis-ci.org/jkraemer/stopwatch.svg?branch=master)](https://travis-ci.org/jkraemer/stopwatch) # Stopwatch Plugin for Redmine [![Test with Redmine](https://github.com/jkraemer/stopwatch/actions/workflows/redmine.yml/badge.svg)](https://github.com/jkraemer/stopwatch/actions/workflows/redmine.yml)
Minimal plugin that aims to make tracking your time with Redmine much easier. Minimal plugin that aims to make tracking your time with Redmine much easier.

View File

@@ -2,9 +2,9 @@
<% time_entry = @time_entries.first %> <% time_entry = @time_entries.first %>
<li> <li>
<% if User.current.is_running_timer? time_entry %> <% if User.current.is_running_timer? time_entry %>
<%= context_menu_link l(:label_stopwatch_stop), stop_stopwatch_timer_path(time_entry, context: '1'), class: 'icon icon-time', remote: true, method: :put %> <%= context_menu_link sprite_icon(:time, l(:label_stopwatch_stop)), stop_stopwatch_timer_path(time_entry, context: '1'), class: 'icon', remote: true, method: :put %>
<% else %> <% else %>
<%= context_menu_link l(:label_stopwatch_start), start_stopwatch_timer_path(time_entry, context: '1'), class: 'icon icon-time', remote: true, method: :put %> <%= context_menu_link sprite_icon(:time, l(:label_stopwatch_start)), start_stopwatch_timer_path(time_entry, context: '1'), class: 'icon', remote: true, method: :put %>
<% end %> <% end %>
</li> </li>
<% end %> <% end %>

View File

@@ -1,6 +1,6 @@
contextMenuHide(); contextMenuHide();
window.stopwatch.updateStartStopLink( window.stopwatch.updateStartStopLink(
'#stopwatch_stop_timer_<%= @issue.id %>', '#stopwatch_stop_timer_<%= @issue.id %>',
'<%= j Stopwatch::IssueLinks.new(@issue).start_timer %>' '<%= j Stopwatch::IssueLinks.new(@issue, self).start_timer %>'
); );
window.stopwatch.timerStopped(); window.stopwatch.timerStopped();

View File

@@ -17,11 +17,11 @@
<td class="hours"><%= html_hours(format_hours(entry.hours)) %></td> <td class="hours"><%= html_hours(format_hours(entry.hours)) %></td>
<td class="buttons"> <td class="buttons">
<% if running %> <% if running %>
<%= link_to t(:label_stopwatch_stop), stop_stopwatch_timer_path(entry), remote: true, class: "icon-only icon-time", method: :put %> <%= link_to sprite_icon(:time, t(:label_stopwatch_stop)), stop_stopwatch_timer_path(entry), remote: true, class: "icon-only", method: :put %>
<% else %> <% else %>
<%= link_to t(:label_stopwatch_start), start_stopwatch_timer_path(entry), remote: true, class: "icon-only icon-time", method: :put %> <%= link_to sprite_icon(:time, t(:label_stopwatch_start)), start_stopwatch_timer_path(entry), remote: true, class: "icon-only", method: :put %>
<% end %> <% end %>
<%= link_to l(:button_edit), edit_stopwatch_timer_path(entry), remote: true, class: "icon-only icon-edit" if entry.editable_by? User.current %> <%= link_to sprite_icon(:edit, l(:button_edit)), edit_stopwatch_timer_path(entry), remote: true, class: "icon-only" if entry.editable_by? User.current %>
</td> </td>
</tr> </tr>
<% end -%> <% end -%>

View File

@@ -74,7 +74,7 @@ window.initStopwatch = function(config){
$('a.stopwatch_issue_timer').each(function(){ $('a.stopwatch_issue_timer').each(function(){
var a = $(this); var a = $(this);
a.attr('href', a.attr('href').replace(/stop$/, 'start')); a.attr('href', a.attr('href').replace(/stop$/, 'start'));
a.text(locales.startTimer); a.find('span').text(locales.startTimer);
}); });
}, },
timerStarted: function(data){ timerStarted: function(data){
@@ -94,10 +94,10 @@ window.initStopwatch = function(config){
if(data.issue_id) { if(data.issue_id) {
if(a.data('issueId') == data.issue_id) { if(a.data('issueId') == data.issue_id) {
a.attr('href', href.replace(/start$/, 'stop')); a.attr('href', href.replace(/start$/, 'stop'));
a.text(locales.stopTimer); a.find('span').text(locales.stopTimer);
} else { } else {
a.attr('href', href.replace(/stop$/, 'start')); a.attr('href', href.replace(/stop$/, 'start'));
a.text(locales.startTimer); a.find('span').text(locales.startTimer);
} }
} }
}); });

15
init.rb
View File

@@ -1,14 +1,11 @@
require_dependency 'stopwatch'
require 'stopwatch/hooks'
Redmine::Plugin.register :stopwatch do Redmine::Plugin.register :stopwatch do
name 'Redmine Stopwatch Plugin' name 'Redmine Stopwatch Plugin'
author 'Jens Krämer' author 'Jens Krämer'
author_url 'https://jkraemer.net/' author_url 'https://jkraemer.net/'
description "Start/stop timer and quick access to today's time bookings for Redmine" description "Start/stop timer and quick access to today's time bookings for Redmine"
version '0.2.0' version '1.1.0'
requires_redmine version_or_higher: '3.4.0' requires_redmine version_or_higher: '6.0.0'
settings default: { settings default: {
'default_activity' => 'always_ask', 'default_activity' => 'always_ask',
}, partial: 'stopwatch/settings' }, partial: 'stopwatch/settings'
@@ -21,10 +18,4 @@ Redmine::Plugin.register :stopwatch do
if: ->(*_){ User.current.logged? and User.current.allowed_to?(:log_time, nil, global: true) } if: ->(*_){ User.current.logged? and User.current.allowed_to?(:log_time, nil, global: true) }
end end
Rails.configuration.to_prepare do Stopwatch.setup
Stopwatch::ContextMenusControllerPatch.apply
Stopwatch::IssuesControllerPatch.apply
Stopwatch::TimeEntryPatch.apply
Stopwatch::UserPatch.apply
end

View File

@@ -1,6 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
module Stopwatch module Stopwatch
def self.setup
Stopwatch::ContextMenusControllerPatch.apply
Stopwatch::IssuesControllerPatch.apply
::TimeEntry.prepend Stopwatch::TimeEntryPatch
::User.prepend Stopwatch::UserPatch
Stopwatch::Hooks # just load it
end
def self.settings def self.settings
Setting.plugin_stopwatch Setting.plugin_stopwatch
end end

View File

@@ -10,9 +10,9 @@ module Stopwatch
User.current.allowed_to?(:log_time, issue.project) User.current.allowed_to?(:log_time, issue.project)
t = Stopwatch::IssueTimer.new(issue: issue) t = Stopwatch::IssueTimer.new(issue: issue)
if t.running? if t.running?
link << IssueLinks.new(issue).stop_timer link << IssueLinks.new(issue, self).stop_timer
else else
link << IssueLinks.new(issue).start_timer link << IssueLinks.new(issue, self).start_timer
end end
end end
super + content_tag(:li, link.html_safe) super + content_tag(:li, link.html_safe)
@@ -26,4 +26,3 @@ module Stopwatch
end end
end end
end end

View File

@@ -1,26 +1,25 @@
module Stopwatch module Stopwatch
class IssueLinks < Struct.new(:issue) class IssueLinks < Struct.new(:issue, :context)
include ActionView::Helpers::UrlHelper
include Rails.application.routes.url_helpers
def start_timer def start_timer
link_to(I18n.t(:label_stopwatch_start), context.link_to(
start_issue_timer_path(issue), context.sprite_icon(:time, I18n.t(:label_stopwatch_start)),
class: 'icon icon-time stopwatch_issue_timer', context.start_issue_timer_path(issue),
data: { issue_id: issue.id }, class: 'icon stopwatch_issue_timer',
remote: true, data: { issue_id: issue.id },
method: 'post') remote: true,
method: 'post'
)
end end
def stop_timer def stop_timer
link_to(I18n.t(:label_stopwatch_stop), context.link_to(
stop_issue_timer_path(issue), context.sprite_icon(:time, I18n.t(:label_stopwatch_stop)),
class: 'icon icon-time stopwatch_issue_timer', context.stop_issue_timer_path(issue),
data: { issue_id: issue.id }, class: 'icon stopwatch_issue_timer',
remote: true, data: { issue_id: issue.id },
method: 'post') remote: true,
method: 'post'
)
end end
# to make route helpers happy # to make route helpers happy

View File

@@ -8,9 +8,9 @@ module Stopwatch
if User.current.allowed_to?(:log_time, issue.project) if User.current.allowed_to?(:log_time, issue.project)
t = Stopwatch::IssueTimer.new(issue: issue) t = Stopwatch::IssueTimer.new(issue: issue)
if t.running? if t.running?
link << IssueLinks.new(issue).stop_timer link << IssueLinks.new(issue, self).stop_timer
else else
link << IssueLinks.new(issue).start_timer link << IssueLinks.new(issue, self).start_timer
end end
end end
link.html_safe + super link.html_safe + super

View File

@@ -1,13 +1,9 @@
module Stopwatch module Stopwatch
module TimeEntryPatch module TimeEntryPatch
def self.apply extend ActiveSupport::Concern
TimeEntry.prepend self unless TimeEntry < self
end
def self.prepended(base) prepended do
base.class_eval do before_destroy :stop_timer
before_destroy :stop_timer
end
end end
def stop_timer def stop_timer

View File

@@ -1,8 +1,6 @@
module Stopwatch module Stopwatch
module UserPatch module UserPatch
def self.apply extend ActiveSupport::Concern
User.prepend self unless User < self
end
def timer_running? def timer_running?
Stopwatch::Timer.new(self).running? Stopwatch::Timer.new(self).running?

View File

@@ -1,4 +1,4 @@
require File.expand_path('../../test_helper', __FILE__) require File.expand_path('../test_helper', __dir__)
class TicketTimerTest < Redmine::IntegrationTest class TicketTimerTest < Redmine::IntegrationTest
include ActiveJob::TestHelper include ActiveJob::TestHelper
@@ -30,6 +30,7 @@ class TicketTimerTest < Redmine::IntegrationTest
assert_not_running assert_not_running
get "/issues/1" get "/issues/1"
assert_response :success
assert_select "div.contextual a", text: /start tracking/i assert_select "div.contextual a", text: /start tracking/i
assert_no_difference ->{TimeEntry.count} do assert_no_difference ->{TimeEntry.count} do
post "/issues/1/timer/start", xhr: true post "/issues/1/timer/start", xhr: true
@@ -77,7 +78,7 @@ class TicketTimerTest < Redmine::IntegrationTest
TimeEntry.delete_all TimeEntry.delete_all
assert_no_difference ->{TimeEntry.count} do assert_no_difference ->{TimeEntry.count} do
post "/issues/1/timer/start", xhr: true post "/issues/1/timer/start", xhr: true
assert_response 200 assert_response :ok
end end
end end
@@ -86,7 +87,7 @@ class TicketTimerTest < Redmine::IntegrationTest
TimeEntry.delete_all TimeEntry.delete_all
with_settings plugin_stopwatch: { 'default_activity' => 'system'} do with_settings plugin_stopwatch: { 'default_activity' => 'system'} do
post "/issues/1/timer/start", xhr: true post "/issues/1/timer/start", xhr: true
assert_response 201 assert_response :created
end end
assert te = TimeEntry.last assert te = TimeEntry.last
assert_equal 1, te.issue_id assert_equal 1, te.issue_id
@@ -98,18 +99,17 @@ class TicketTimerTest < Redmine::IntegrationTest
TimeEntry.delete_all TimeEntry.delete_all
with_settings plugin_stopwatch: { 'default_activity' => '9'} do with_settings plugin_stopwatch: { 'default_activity' => '9'} do
post "/issues/1/timer/start", xhr: true post "/issues/1/timer/start", xhr: true
assert_response 201 assert_response :created
end end
assert te = TimeEntry.last assert te = TimeEntry.last
assert_equal 1, te.issue_id assert_equal 1, te.issue_id
assert_equal 9, te.activity_id assert_equal 9, te.activity_id
end end
private private
def assert_not_running def assert_not_running
refute Stopwatch::Timer.new(User.find(@user.id)).running? assert_not Stopwatch::Timer.new(User.find(@user.id)).running?
end end
def assert_running def assert_running

View File

@@ -1,5 +0,0 @@
# for travis debugging
# config.logger = Logger.new(STDOUT)
# config.logger.level = Logger::INFO
# config.log_level = :info

View File

@@ -1,8 +0,0 @@
test:
adapter: postgresql
encoding: unicode
pool: 5
database: travis_ci_test
user: postgres

View File

@@ -1,4 +1,4 @@
require_relative '../test_helper' require File.expand_path('../../test_helper', __FILE__)
class StartTimerTest < ActiveSupport::TestCase class StartTimerTest < ActiveSupport::TestCase
fixtures :users, :user_preferences, :time_entries, :projects, fixtures :users, :user_preferences, :time_entries, :projects,

View File

@@ -1,4 +1,4 @@
require_relative '../test_helper' require File.expand_path('../../test_helper', __FILE__)
class StopTimerTest < ActiveSupport::TestCase class StopTimerTest < ActiveSupport::TestCase
fixtures :users, :user_preferences, :time_entries, :projects, fixtures :users, :user_preferences, :time_entries, :projects,

View File

@@ -1,4 +1,4 @@
require_relative '../test_helper' require File.expand_path('../../test_helper', __FILE__)
class StopwatchTest < ActiveSupport::TestCase class StopwatchTest < ActiveSupport::TestCase
fixtures :projects, :enabled_modules, :enumerations fixtures :projects, :enabled_modules, :enumerations

View File

@@ -1,4 +1,4 @@
require_relative '../test_helper' require File.expand_path('../../test_helper', __FILE__)
class StartTimerTest < ActiveSupport::TestCase class StartTimerTest < ActiveSupport::TestCase
fixtures :users, :user_preferences, :time_entries, :projects, fixtures :users, :user_preferences, :time_entries, :projects,

View File

@@ -1,4 +1,4 @@
require_relative '../test_helper' require File.expand_path('../../test_helper', __FILE__)
class TimerTest < ActiveSupport::TestCase class TimerTest < ActiveSupport::TestCase
fixtures :users, :user_preferences, :time_entries fixtures :users, :user_preferences, :time_entries

View File

@@ -1,4 +1,4 @@
require_relative '../test_helper' require File.expand_path('../../test_helper', __FILE__)
class UserTest < ActiveSupport::TestCase class UserTest < ActiveSupport::TestCase
fixtures :users, :user_preferences, :issues fixtures :users, :user_preferences, :issues

View File

@@ -1,75 +0,0 @@
#/bin/bash
set -e
if [[ ! "$TESTSPACE" = /* ]] ||
[[ ! "$PATH_TO_REDMINE" = /* ]] ||
[[ ! "$REDMINE_VER" = * ]] ||
[[ ! "$NAME_OF_PLUGIN" = * ]] ||
[[ ! "$PATH_TO_PLUGIN" = /* ]];
then
echo "You should set"\
" TESTSPACE, PATH_TO_REDMINE, REDMINE_VER"\
" NAME_OF_PLUGIN, PATH_TO_PLUGIN"\
" environment variables"
echo "You set:"\
"$TESTSPACE"\
"$PATH_TO_REDMINE"\
"$REDMINE_VER"\
"$NAME_OF_PLUGIN"\
"$PATH_TO_PLUGIN"
exit 1;
fi
export RAILS_ENV=test
export REDMINE_GIT_REPO=git://github.com/redmine/redmine.git
export REDMINE_GIT_TAG=$REDMINE_VER
export BUNDLE_GEMFILE=$PATH_TO_REDMINE/Gemfile
# checkout redmine
git clone $REDMINE_GIT_REPO $PATH_TO_REDMINE
cd $PATH_TO_REDMINE
if [ ! "$REDMINE_GIT_TAG" = "master" ];
then
git checkout -b $REDMINE_GIT_TAG origin/$REDMINE_GIT_TAG
fi
# create a link to the plugin
ln -sf $PATH_TO_PLUGIN plugins/$NAME_OF_PLUGIN
mv $TESTSPACE/database.yml.travis config/database.yml
mv $TESTSPACE/additional_environment.rb config/
cat << EOF > lib/tasks/00_nowarnings.rake
require 'rake/testtask'
module NoWarnings
def define(*_)
self.warning = false
super
end
end
Rake::TestTask.prepend NoWarnings
EOF
# install gems
bundle install
# run redmine database migrations
bundle exec rake db:migrate
# run plugin database migrations
bundle exec rake redmine:plugins:migrate
# install redmine database
#bundle exec rake redmine:load_default_data REDMINE_LANG=en
bundle exec rake db:structure:dump
# run tests
# bundle exec rake TEST=test/unit/role_test.rb
bundle exec rake redmine:plugins:test NAME=$NAME_OF_PLUGIN