From 42e46fe18568feb274153ef58e3ab791ba35eb07 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sat, 21 Mar 2026 17:42:02 +0000
Subject: [PATCH 01/12] Fix HTTP 500 error when invalid date is entered in
workload filter
Wrap the `to_date` conversion in `sanitizeDateParameter` with a
begin-rescue block to catch `Date::Error`. Previously, strings like
"2026-01-32" passed the `respond_to?(:to_date)` check but raised an
unhandled exception during conversion. Now invalid dates fall back to
the default value instead of crashing.
Adds functional tests for all three date parameters (first_day,
last_day, start_date) to cover the invalid date case.
Fixes https://github.com/xmera-circle/redmine_workload/issues/41
https://claude.ai/code/session_01BSqVnAYvK1kCejeMnp4MqM
---
app/controllers/workloads_controller.rb | 10 ++++----
test/functional/workloads_controller_test.rb | 27 ++++++++++++++++++++
2 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/app/controllers/workloads_controller.rb b/app/controllers/workloads_controller.rb
index 3d2085c..93e96d6 100644
--- a/app/controllers/workloads_controller.rb
+++ b/app/controllers/workloads_controller.rb
@@ -110,10 +110,10 @@ def assignee_ids
end
def sanitizeDateParameter(parameter, default)
- if parameter.respond_to?(:to_date)
- parameter.to_date
- else
- default
- end
+ return default unless parameter.respond_to?(:to_date)
+
+ parameter.to_date
+ rescue Date::Error
+ default
end
end
diff --git a/test/functional/workloads_controller_test.rb b/test/functional/workloads_controller_test.rb
index ec894b0..644d6e6 100644
--- a/test/functional/workloads_controller_test.rb
+++ b/test/functional/workloads_controller_test.rb
@@ -43,5 +43,32 @@ class WorkloadsControllerTest < ActionDispatch::IntegrationTest
assert flash[:error].match(/Character encoding not allowed./)
end
+
+ test 'should get index with invalid first_day date without raising an error' do
+ manager = roles :roles_001
+ manager.add_permission! :view_all_workloads
+ log_user('jsmith', 'jsmith')
+
+ get workloads_path(workload: { first_day: '2026-01-32' })
+ assert_response :success
+ end
+
+ test 'should get index with invalid last_day date without raising an error' do
+ manager = roles :roles_001
+ manager.add_permission! :view_all_workloads
+ log_user('jsmith', 'jsmith')
+
+ get workloads_path(workload: { last_day: '2026-13-01' })
+ assert_response :success
+ end
+
+ test 'should get index with invalid start_date without raising an error' do
+ manager = roles :roles_001
+ manager.add_permission! :view_all_workloads
+ log_user('jsmith', 'jsmith')
+
+ get workloads_path(workload: { start_date: 'not-a-date' })
+ assert_response :success
+ end
end
end
From ca4be9dcc7b7821cdd8cd353b93d91e6b0fcbf2c Mon Sep 17 00:00:00 2001
From: Claude
Date: Sat, 21 Mar 2026 18:09:35 +0000
Subject: [PATCH 02/12] Fix HTTP 500 when 'Use as today' date is after end date
When the user sets a 'Use as today' (start_date) value beyond the
last_day of the displayed time span, the UserWorkload model crashes
with a NoMethodError because 'today' falls outside the time_span and
hours_for_issue[today] returns nil.
Cap @today to @last_day in the controller before passing it to
UserWorkload. Show a flash warning when the capping occurs so the
user is informed.
Fixes #40
https://claude.ai/code/session_01BSqVnAYvK1kCejeMnp4MqM
---
app/controllers/workloads_controller.rb | 7 ++++++
config/locales/de.yml | 1 +
config/locales/en.yml | 1 +
test/functional/workloads_controller_test.rb | 26 ++++++++++++++++++++
4 files changed, 35 insertions(+)
diff --git a/app/controllers/workloads_controller.rb b/app/controllers/workloads_controller.rb
index 93e96d6..9040eee 100644
--- a/app/controllers/workloads_controller.rb
+++ b/app/controllers/workloads_controller.rb
@@ -32,6 +32,12 @@ def index
# Make sure that last_day is at most 12 months after first_day to prevent
# long running times
@last_day = [(@first_day >> 12) - 1, @last_day].min
+
+ # Make sure that today is not after last_day to prevent a crash in the
+ # workload calculation (today would be outside the time span)
+ @today_capped = @today > @last_day
+ @today = [@today, @last_day].min
+
@time_span_to_display = @first_day..@last_day
if @date_check
@@ -56,6 +62,7 @@ def index
respond_to do |format|
format.html do
flash.now[:error] = l(:error_date_setting) unless @date_check
+ flash.now[:warning] = l(:warning_today_capped_to_last_day) if @today_capped
render action: :index
end
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 0aee4b8..5a28ec0 100755
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -100,6 +100,7 @@ de:
workload_unscheduled_issues_num: 'Anzahl ungeplanter Tickets:'
workload_unscheduled_issues_hours: 'Stunden aus ungeplanten Tickets:'
error_date_setting: 'Überprüfen Sie Ihre Eingabe! Das Enddatum (bis) liegt vor dem Startdatum (von).'
+ warning_today_capped_to_last_day: 'Das "Als Heute verwenden"-Datum lag nach dem Enddatum und wurde auf das Enddatum gesetzt.'
error_encoding_setting: 'Zeichenkodierung nicht erlaubt. Gültige Werte: %{value}.'
label_workload_calculation: Workloadberechnung
label_include_parent_tasks: Hauptaufgaben einbeziehen
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 670b832..b111ff9 100755
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -100,6 +100,7 @@ en:
workload_unscheduled_issues_num: 'Number of unscheduled issues:'
workload_unscheduled_issues_hours: 'Hours of unscheduled issues:'
error_date_setting: 'Please check your data! The end date is before the start date.'
+ warning_today_capped_to_last_day: 'The "Use as today" date was after the end date and has been set to the end date.'
error_encoding_setting: 'Character encoding not allowed. Valid values: %{value}.'
label_workload_calculation: Workload calculation
label_include_parent_tasks: Include parent tasks
diff --git a/test/functional/workloads_controller_test.rb b/test/functional/workloads_controller_test.rb
index 644d6e6..c039a85 100644
--- a/test/functional/workloads_controller_test.rb
+++ b/test/functional/workloads_controller_test.rb
@@ -70,5 +70,31 @@ class WorkloadsControllerTest < ActionDispatch::IntegrationTest
get workloads_path(workload: { start_date: 'not-a-date' })
assert_response :success
end
+
+ test 'should get index without error when start_date is after last_day' do
+ manager = roles :roles_001
+ manager.add_permission! :view_all_workloads
+ log_user('jsmith', 'jsmith')
+
+ get workloads_path(workload: {
+ first_day: '2026-01-01',
+ last_day: '2026-01-31',
+ start_date: '2026-06-01'
+ })
+ assert_response :success
+ end
+
+ test 'should show warning when start_date is after last_day' do
+ manager = roles :roles_001
+ manager.add_permission! :view_all_workloads
+ log_user('jsmith', 'jsmith')
+
+ get workloads_path(workload: {
+ first_day: '2026-01-01',
+ last_day: '2026-01-31',
+ start_date: '2026-06-01'
+ })
+ assert flash[:warning].present?
+ end
end
end
From 62abdab04e3ae4eca85fd7deef4e769594545d3e Mon Sep 17 00:00:00 2001
From: Markus Boremski
Date: Wed, 26 Aug 2026 08:13:48 +0000
Subject: [PATCH 03/12] CI: run tests against Redmine 6.1-stable
The 4.x line requires Redmine 6: migrations were bumped to
ActiveRecord::Migration[7.2] in 4.0.0, which Rails 6.1 (Redmine 5.0)
rejects with `Unknown migration version "7.2"`. The workflow therefore
could not pass against 5.0-stable any more.
Changes:
* Rename 5-0-stable.yml to 6-1-stable.yml and export
branches/6.1-stable instead of 5.0-stable.
* Bump Ruby to 3.2. Redmine 6.1 requires `ruby '>= 3.2.0', '< 3.5.0'`,
so `bundle install` fails on 3.1.
* Add `rake assets:precompile` after the migrations. Redmine 6 uses
Propshaft.
* actions/checkout v3 -> v4.
Workflow name ("GitHub CI") and job id (plugin_tests) are unchanged so
existing required status checks keep matching. Redmine 5 remains served
by the 3.x line.
README: point the CI badge at the renamed workflow.
---
.github/workflows/{5-0-stable.yml => 6-1-stable.yml} | 10 +++++++---
README.md | 2 +-
2 files changed, 8 insertions(+), 4 deletions(-)
rename .github/workflows/{5-0-stable.yml => 6-1-stable.yml} (94%)
diff --git a/.github/workflows/5-0-stable.yml b/.github/workflows/6-1-stable.yml
similarity index 94%
rename from .github/workflows/5-0-stable.yml
rename to .github/workflows/6-1-stable.yml
index ae1cfc0..6b2f93b 100644
--- a/.github/workflows/5-0-stable.yml
+++ b/.github/workflows/6-1-stable.yml
@@ -1,5 +1,8 @@
# This configuration is taken from Redmine DMSF plugin
# and adapted to the needs of Redmine Workload plugin.
+#
+# Targets Redmine 6.1-stable (Rails 7.2). Plugin versions 4.x require
+# Redmine 6; for Redmine 5 use the 3.x line.
#
# Copyright © 2023 Liane Hampe
# Copyright © 2022-23 Karel Pičman
@@ -68,16 +71,16 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y subversion
- name: Clone Redmine
# Get the latest stable Redmine
- run: svn export http://svn.redmine.org/redmine/branches/5.0-stable/ redmine
+ run: svn export http://svn.redmine.org/redmine/branches/6.1-stable/ redmine
- name: Checkout plugin
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
path: redmine/plugins/${{ env.NAME }}
- name: Install Ruby and gems
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- ruby-version: '3.1'
+ ruby-version: '3.2'
- name: Setup database
# Create the database
run: |
@@ -100,6 +103,7 @@ jobs:
bundle exec rake db:migrate
bundle exec rake redmine:plugins:migrate
bundle exec rake redmine:load_default_data
+ bundle exec rake assets:precompile
env:
REDMINE_LANG: en
working-directory: redmine
diff --git a/README.md b/README.md
index 99c64be..99d90e1 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Workload Plugin for Redmine
-    
+    
A complete rewrite of the original workload-plugin from Rafael Calleja.
The plugin calculates how much work each user would have to do per day in order to hit the deadlines for all his issues.
From 0c1c1c4721ad07226586fa0beb94f5934b41b51c Mon Sep 17 00:00:00 2001
From: Markus Boremski
Date: Wed, 26 Aug 2026 08:31:03 +0000
Subject: [PATCH 04/12] Fix test_helper for Rails 7.2: do not use Rails.root at
load time
`rake redmine:plugins:test:units` runs the tests through
`Rails::TestUnit::Runner.run_from_rake`, which spawns `bin/rails test` in
a separate process. That process requires the test files *before* the
Rails environment is loaded, and on Rails 7.2 `rails/commands` no longer
pulls in railties' `rails.rb` on the way. `Rails` is then only a bare
namespace module and `Rails.root` does not exist:
test/test_helper.rb:4:in `':
undefined method `root' for Rails:Module (NoMethodError)
On Rails 6.1 (Redmine 5.0) `rails.rb` happened to be loaded by then, so
the same line worked. Resolve Redmine's test_helper by path instead,
which is what Redmine's own plugins do and what the environment does not
have to be loaded for.
plugins//test/test_helper.rb -> ../../../test/test_helper
---
test/test_helper.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 3c00634..ea5ed43 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Load the normal Rails helper
-require Rails.root.join('test/test_helper.rb')
+require File.expand_path('../../../test/test_helper', __dir__)
# Load other test helper modules
require File.expand_path('authenticate_user', __dir__)
require File.expand_path('workload_object_helper', __dir__)
From 08c7604ade0cc6fa97daa669a9043938679690f7 Mon Sep 17 00:00:00 2001
From: Markus Boremski
Date: Wed, 26 Aug 2026 08:31:13 +0000
Subject: [PATCH 05/12] CI: upload test.log when a run fails
The plugin test tasks print the failure but not the surrounding log.
Attaching test.log as an artifact on failure makes the next iteration
diagnosable without reproducing locally. Taken from the DMSF workflow.
---
.github/workflows/6-1-stable.yml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/.github/workflows/6-1-stable.yml b/.github/workflows/6-1-stable.yml
index 6b2f93b..6a383f8 100644
--- a/.github/workflows/6-1-stable.yml
+++ b/.github/workflows/6-1-stable.yml
@@ -114,6 +114,14 @@ jobs:
bundle exec rake redmine:plugins:test:units
bundle exec rake redmine:plugins:test:functionals
bundle exec rake redmine:plugins:test:integration
+ - name: Archive test.log
+ # Keep the log so failing runs can be diagnosed
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: "test_${{matrix.engine}}.log"
+ path: redmine/log/test.log
+ if-no-files-found: ignore
- name: Cleanup
# Rollback plugin's changes to the database
# Stop the database engine
From 7575e0a876dd825ee6f200b8d0df6dc6573e3e91 Mon Sep 17 00:00:00 2001
From: Markus Boremski
Date: Wed, 26 Aug 2026 08:46:26 +0000
Subject: [PATCH 06/12] Fix WlUserSelectionTest for Redmine 6 fixture loading
Two tests failed with exactly one extra user:
WlUserSelectionTest#test_should_return_all_users_if_the_current_user_is_admin
WlUserSelectionTest#test_should_return_all_active_users_when_user_has_permission_:view_all_workloads
Redmine 6.0 added `fixtures :all` to ActiveSupport::TestCase in
test/test_helper.rb; Redmine 5.0 did not have it. Every fixture file is
now loaded no matter what a test class declares, including
groups_users.yml, which makes users(:users_008) a member of groups 10
and 11.
`WlUserSelection#all_users` is `User.joins(:groups).distinct.active` --
every user belonging to any group. Under Redmine 6 that legitimately
includes users(:users_008), so comparing against only the users
generated in setup is wrong. Confirmed in the CI log:
WlUserData Load ... WHERE `wl_user_datas`.`user_id` IN (8, 41, 42, 43)
The plugin behaves correctly; only the tests' assumption about the
fixture universe was stale. Record the pre-existing group members in
setup and add them to the expectation, walking Group#users rather than
reusing the scope under test.
Note that the per-class `fixtures ...` declarations throughout this
plugin's test suite are effectively inert on Redmine 6 for the same
reason. They are left alone here.
---
test/unit/user_selection_test.rb | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/test/unit/user_selection_test.rb b/test/unit/user_selection_test.rb
index 77411d0..cb40dc2 100644
--- a/test/unit/user_selection_test.rb
+++ b/test/unit/user_selection_test.rb
@@ -10,6 +10,15 @@ class WlUserSelectionTest < ActiveSupport::TestCase
:users, :issue_statuses, :enumerations, :roles
def setup
+ # Redmine >= 6.0 declares `fixtures :all` in its own test_helper, so every
+ # fixture file is loaded regardless of what a test class asks for. Among
+ # them is groups_users.yml, which makes users(:users_008) a member of two
+ # groups. Queries for "all users belonging to any group" therefore no
+ # longer return only the users generated below. Record what is already
+ # there before adding anything.
+ @fixture_group_member_ids =
+ Group.all.flat_map { |group| group.users.select(&:active?) }.map(&:id).uniq
+
@group1 = Group.generate!
@group2 = Group.generate!
@group3 = Group.generate!
@@ -26,11 +35,19 @@ def setup
@group_member_ids
end
+ ##
+ # All users a global workload query may return: the ones generated in setup
+ # plus the group members that come from Redmine's own fixtures.
+ #
+ def all_group_member_ids
+ (@fixture_group_member_ids + @group_member_ids.flatten).uniq.sort
+ end
+
test 'should return all users if the current user is admin' do
current_user = User.generate!(admin: true)
groups = WlGroupSelection.new(user: current_user, groups: [@group1.id, @group2.id, @group3.id])
users = WlUserSelection.new(user: current_user, group_selection: groups)
- assert_equal @group_member_ids.flatten.sort, users.allowed_to_display.map(&:id).sort
+ assert_equal all_group_member_ids, users.allowed_to_display.map(&:id).sort
end
test 'should return all active users when user has permission :view_all_workloads' do
@@ -40,7 +57,7 @@ def setup
groups = WlGroupSelection.new(user: current_user, groups: [@group1.id, @group2.id, @group3.id])
users = WlUserSelection.new(user: current_user, group_selection: groups)
- expected = @group_member_ids.flatten.sort
+ expected = all_group_member_ids
current = users.send(:all_users).map(&:id).sort
assert_equal expected, current
end
From 86f220de34bbe391ed431b9a41b32a27498bbcec Mon Sep 17 00:00:00 2001
From: Markus Boremski
Date: Wed, 26 Aug 2026 08:13:48 +0000
Subject: [PATCH 07/12] Declare Redmine 6.1 as the supported version
4.0.0 announced Redmine 6.0.x, but nothing in the repository ever tested
against Redmine 6 and the plugin had no `requires_redmine` at all, so an
incompatible install failed with a stack trace instead of a clear message.
* init.rb: add `requires_redmine version_or_higher: '6.1'`.
* README/CHANGELOG: state 6.1.z, the version the CI workflow covers.
Redmine 6.0.z ships the same Rails 7.2 and probably works, but it is
untested and therefore not claimed.
* README: add a "support of Redmine 6" section spelling out that 4.x is
not backward compatible (Migration[7.2]) and that Redmine 5 stays on
the 3.x line.
* README: the PostgreSQL/Ruby 3.1 warning is moot now that Redmine 6.1
requires Ruby >= 3.2; keep the note but say so.
---
CHANGELOG.md | 4 +++-
README.md | 16 ++++++++++++++--
init.rb | 1 +
3 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9094923..bfd71a9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,12 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
-* Updated for Redmine 6.0.x / Rails 7.2 compatibility
+* Updated for Redmine 6.1.x / Rails 7.2 compatibility
* Fixed Rails version comparisons to use Gem::Version
* Updated database adapter detection for Rails 7.2
* Updated ActiveRecord migrations to version 7.2
* Removed deprecated `unloadable` from controllers and models
* Fixed Ruby version comparison for PostgreSQL requirement
+* Declared `requires_redmine version_or_higher: '6.1'`
+* CI now runs against Redmine 6.1-stable on Ruby 3.2 instead of 5.0-stable
## 3.0.2 - 2023-07-24
diff --git a/README.md b/README.md
index 99d90e1..016ccd1 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Workload Plugin for Redmine
-    
+    
A complete rewrite of the original workload-plugin from Rafael Calleja.
The plugin calculates how much work each user would have to do per day in order to hit the deadlines for all his issues.
@@ -33,7 +33,19 @@ The encoding options are the same as on the page itself and might depend on your
Workday settings are fixed now (see [#27](https://github.com/xmera-circle/redmine_workload/issues/27)) but lead to restrictions for PostgreSQL user.
- :warning: **With PostgreSQL installed you need to run Ruby 3.1.z!**
+ :warning: **PostgreSQL requires Ruby 3.1 or newer.** Redmine 6.1 requires Ruby 3.2
+or newer anyway, so this is no longer a separate constraint for the 4.x line.
+
+## New Features in Version 4.0.0
+
+### support of Redmine 6
+
+Version 4.0.0 supports Redmine 6.1.z and is **not** backward compatible: the
+database migrations are on `ActiveRecord::Migration[7.2]`, which Redmine 5
+(Rails 6.1) cannot load. Installations on Redmine 5 stay on the 3.x line.
+
+Redmine 6.0.z ships the same Rails 7.2 and is likely to work, but it is not
+covered by the test workflow and therefore not claimed as supported.
## New Features in Version 2.2.0
diff --git a/init.rb b/init.rb
index 2d68e56..7e6923a 100755
--- a/init.rb
+++ b/init.rb
@@ -11,6 +11,7 @@
'all their assigned issus on time.'
version '4.0.0'
url 'https://github.com/xmera-circle/redmine_workload'
+ requires_redmine version_or_higher: '6.1'
if RedmineWorkload.postgresql? && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.1.0')
msg = "#{name} requires at least Ruby 3.1.0 when using postgresql database."
From 9e80dfc571a9d0886d44be1302aadac86030cc54 Mon Sep 17 00:00:00 2001
From: Markus Boremski
Date: Wed, 26 Aug 2026 08:13:48 +0000
Subject: [PATCH 08/12] Remove code that cannot run on Redmine 6
With `requires_redmine >= 6.1` the plugin always runs on Rails 7.2 and
Ruby >= 3.2, which makes several branches unreachable:
* init.rb: the `Rails.version < '6'` block added `app/presenters` to
`autoload_paths`. That directory does not exist in this repository, so
the branch was dead even on Rails 5.
* user_patch.rb: the `to_prepare` fallback for Rails < 6. The
after_plugins_loaded hook does this on every supported version.
* after_plugins_loaded_hook.rb: the Rails >= 6 guard is now always true.
* init.rb / lib/redmine_workload.rb: the PostgreSQL Ruby-version guard
and `RedmineWorkload.postgresql?`, its only caller. Redmine 6.1
requires Ruby >= 3.2, so the guard can never fire. If a PostgreSQL
specific check is needed again, reinstate it with a real reason.
No behaviour change on Redmine 6.
---
CHANGELOG.md | 6 ++++++
init.rb | 13 -------------
lib/redmine_workload.rb | 14 --------------
lib/redmine_workload/extensions/user_patch.rb | 9 ---------
.../hooks/after_plugins_loaded_hook.rb | 2 --
5 files changed, 6 insertions(+), 38 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bfd71a9..59f7569 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Declared `requires_redmine version_or_higher: '6.1'`
* CI now runs against Redmine 6.1-stable on Ruby 3.2 instead of 5.0-stable
+### Removed
+
+* Dead `Rails.version < '6'` branches in `init.rb` and `user_patch.rb`
+* Ruby version guard for PostgreSQL and `RedmineWorkload.postgresql?`,
+ unreachable since Redmine 6.1 requires Ruby >= 3.2
+
## 3.0.2 - 2023-07-24
### Deletes
diff --git a/init.rb b/init.rb
index 7e6923a..31f8df1 100755
--- a/init.rb
+++ b/init.rb
@@ -13,11 +13,6 @@
url 'https://github.com/xmera-circle/redmine_workload'
requires_redmine version_or_higher: '6.1'
- if RedmineWorkload.postgresql? && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.1.0')
- msg = "#{name} requires at least Ruby 3.1.0 when using postgresql database."
- raise Redmine::PluginRequirementError, msg
- end
-
menu :top_menu,
:WorkLoad,
{ controller: 'workloads', action: 'index' },
@@ -50,14 +45,6 @@
permission :edit_user_data, wl_user_datas: :update
end
-# Rails 6+ handles autoloading differently with Zeitwerk
-if Gem::Version.new(Rails.version) < Gem::Version.new('6.0')
- plugin = Redmine::Plugin.find(:redmine_workload)
- Rails.application.configure do
- config.autoload_paths << "#{plugin.directory}/app/presenters"
- end
-end
-
class RedmineToolbarHookListener < Redmine::Hook::ViewListener
def view_layouts_base_html_head(_context)
javascript_include_tag('slides', plugin: :redmine_workload) +
diff --git a/lib/redmine_workload.rb b/lib/redmine_workload.rb
index f910766..9f8265d 100644
--- a/lib/redmine_workload.rb
+++ b/lib/redmine_workload.rb
@@ -11,17 +11,3 @@
require File.expand_path('redmine_workload/wl_issue_state', __dir__)
require File.expand_path('redmine_workload/wl_user_data_finder', __dir__)
require File.expand_path('redmine_workload/wl_user_data_defaults', __dir__)
-
-# Simple Rails related methods
-module RedmineWorkload
- # Check whether Redmine is running postgresql database
- def self.postgresql?
- db_config = if ActiveRecord::Base.configurations.respond_to?(:configs_for)
- ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first
- else
- ActiveRecord::Base.configurations[Rails.env]
- end
- adapter = db_config.respond_to?(:adapter) ? db_config.adapter : db_config['adapter']
- adapter == 'postgresql'
- end
-end
diff --git a/lib/redmine_workload/extensions/user_patch.rb b/lib/redmine_workload/extensions/user_patch.rb
index 03b84c6..568b240 100644
--- a/lib/redmine_workload/extensions/user_patch.rb
+++ b/lib/redmine_workload/extensions/user_patch.rb
@@ -27,12 +27,3 @@ def main_group_id
end
end
end
-
-# Rails 6+ uses after_plugins_loaded hook instead
-if Gem::Version.new(Rails.version) < Gem::Version.new('6.0')
- Rails.configuration.to_prepare do
- unless User.included_modules.include?(RedmineWorkload::Extensions::UserPatch)
- User.prepend RedmineWorkload::Extensions::UserPatch
- end
- end
-end
diff --git a/lib/redmine_workload/hooks/after_plugins_loaded_hook.rb b/lib/redmine_workload/hooks/after_plugins_loaded_hook.rb
index fca7dc3..a7172db 100644
--- a/lib/redmine_workload/hooks/after_plugins_loaded_hook.rb
+++ b/lib/redmine_workload/hooks/after_plugins_loaded_hook.rb
@@ -4,8 +4,6 @@ module RedmineWorkload
module Hooks
class AfterPluginsLoadedHook < Redmine::Hook::Listener
def after_plugins_loaded(_context = {})
- return unless Gem::Version.new(Rails.version) >= Gem::Version.new('6.0')
-
patch = RedmineWorkload::Extensions::UserPatch
klass = User
klass.prepend patch unless klass.included_modules.include?(patch)
From ddf321437b6f65529913fdfeab4aabcd85039bb3 Mon Sep 17 00:00:00 2001
From: Markus Boremski
Date: Wed, 26 Aug 2026 10:04:55 +0000
Subject: [PATCH 09/12] CHANGELOG: record the fixes that went into 4.0.0
The two bug fixes and the two test fixes were not listed yet. Keeps the
4.0.0 section a complete description of what changed since 3.0.2.
The 4.0.0 date still reads 2026-02-06 and should be updated when the
version is actually tagged.
---
CHANGELOG.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 59f7569..c49541d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Declared `requires_redmine version_or_higher: '6.1'`
* CI now runs against Redmine 6.1-stable on Ruby 3.2 instead of 5.0-stable
+### Fixed
+
+* HTTP 500 when an invalid date such as `2026-01-32` was entered in a workload
+ filter; invalid input now falls back to the default value (#41)
+* HTTP 500 when the 'Use as today' date was set beyond the last day of the
+ displayed time span; the date is capped and a flash warning is shown (#40)
+* `test/test_helper.rb` no longer uses `Rails.root` at load time. Rails 7.2 runs
+ plugin tests in a separate process that requires the test files before the
+ environment is loaded, where `Rails.root` does not exist
+* `WlUserSelectionTest` accounts for Redmine 6's `fixtures :all`, which makes
+ `users(:users_008)` a member of two groups
+
### Removed
* Dead `Rails.version < '6'` branches in `init.rb` and `user_patch.rb`
From f602debb3956db638b145f18c50b347bd086fc9b Mon Sep 17 00:00:00 2001
From: Markus Boremski
Date: Wed, 26 Aug 2026 14:38:24 +0000
Subject: [PATCH 10/12] Use sprite_icon for the four icon links
Redmine 6 renders icons as SVG sprites. The plain `icon icon-add` class
still works through the legacy fallback (`.icon-add:not(:has(svg))` puts
a PNG behind it), but the result sits next to the core's SVG icons and
looks out of place.
Wrap the labels in `sprite_icon`, keeping the existing css classes, the
same way the core views do (compare app/views/calendars/show.html.erb for
the `link_to_function` case). All three sprite names -- add, checked,
warning -- exist in Redmine 6.1's icons.svg.
---
app/views/settings/_workload_settings.erb | 2 +-
app/views/wl_national_holiday/index.html.erb | 2 +-
app/views/wl_user_vacations/index.html.erb | 2 +-
app/views/workloads/_filters.erb | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/views/settings/_workload_settings.erb b/app/views/settings/_workload_settings.erb
index 6b1d329..950c060 100644
--- a/app/views/settings/_workload_settings.erb
+++ b/app/views/settings/_workload_settings.erb
@@ -140,6 +140,6 @@
<%= "checked" if settings['workload_of_parent_issues'] != '' %>
>
<%= l(:info_include_parent_tasks) %>
- <%= l(:warning_include_parent_tasks) %>
+ <%= sprite_icon('warning', l(:warning_include_parent_tasks)) %>
\ No newline at end of file
diff --git a/app/views/wl_national_holiday/index.html.erb b/app/views/wl_national_holiday/index.html.erb
index d98d61d..e6ef126 100644
--- a/app/views/wl_national_holiday/index.html.erb
+++ b/app/views/wl_national_holiday/index.html.erb
@@ -5,7 +5,7 @@
<%= l(:workload_holiday_title)%>
-<%= link_to l(:label_new), {controller: "wl_national_holiday", action: "new"}, class: "icon icon-add" if @is_allowed%>
+<%= link_to sprite_icon('add', l(:label_new)), {controller: "wl_national_holiday", action: "new"}, class: "icon icon-add" if @is_allowed%>
<%= link_to "<<", :controller => 'wl_national_holiday', :action => "index", :year => @this_year-1 %>
diff --git a/app/views/wl_user_vacations/index.html.erb b/app/views/wl_user_vacations/index.html.erb
index e259f5c..d2d1533 100644
--- a/app/views/wl_user_vacations/index.html.erb
+++ b/app/views/wl_user_vacations/index.html.erb
@@ -4,7 +4,7 @@
<%= l(:workload_user_vacation_site_title) %> » <%= User.current.name %>
-<%= link_to l(:label_new), new_wl_user_vacation_path, :class => 'icon icon-add' if @is_allowed %>
+<%= link_to sprite_icon('add', l(:label_new)), new_wl_user_vacation_path, :class => 'icon icon-add' if @is_allowed %>
<% unless @wl_user_vacations.empty?%>
<%= render(partial: "show_list", locals: {wl_user_vacations: @wl_user_vacations, is_allowed: @is_allowed}) %>
diff --git a/app/views/workloads/_filters.erb b/app/views/workloads/_filters.erb
index 4cd1e81..fba6033 100644
--- a/app/views/workloads/_filters.erb
+++ b/app/views/workloads/_filters.erb
@@ -38,6 +38,6 @@
<%= label_tag :workload_groups, l(:workload_show_filter_group) %>
<%= select_tag :workload_groups, group_options_for_select(@groups&.allowed_to_display, @groups&.selected), :name => 'workload[groups][]', :multiple => true, :onchange => "this.form.workload_users.selectedIndex=-1;" %>
- <%= link_to_function l(:button_apply), 'jQuery("#filter_form").submit()', :class => 'apply icon icon-checked' %>
+ <%= link_to_function sprite_icon('checked', l(:button_apply)), 'jQuery("#filter_form").submit()', :class => 'apply icon icon-checked' %>
<% end %>
From 3a0b0516c3597ec8fb7b556390995a08fab2bd69 Mon Sep 17 00:00:00 2001
From: Markus Boremski
Date: Wed, 26 Aug 2026 14:38:40 +0000
Subject: [PATCH 11/12] Rubocop: target Ruby 3.2
The config still targeted Ruby 2.7. Redmine 6.1 requires
`ruby '>= 3.2.0', '< 3.5.0'`, so 3.2 is the lowest version this plugin
can actually run on.
Rubocop is not part of the CI workflow, so this only affects local runs.
Raising the target may surface new offences; .rubocop_todo.yml was left
untouched.
---
.rubocop.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.rubocop.yml b/.rubocop.yml
index 9e9a678..abafd7c 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -4,7 +4,7 @@ AllCops:
NewCops: enable
DisplayCopNames: true
DisplayStyleGuide: true
- TargetRubyVersion: 2.7
+ TargetRubyVersion: 3.2
Exclude:
- '**/vendor/**/*'
From efa2c50d21c45641d1ae71f8a1383247a8ec0fda Mon Sep 17 00:00:00 2001
From: Markus Boremski
Date: Wed, 26 Aug 2026 14:39:06 +0000
Subject: [PATCH 12/12] Load the plugin's assets only on the plugin's own pages
RedmineToolbarHookListener injected slides.js and style.css into the
layout head of *every* Redmine page, not just the workload views. Two
consequences:
* every page in the installation paid for two extra requests it has no
use for
* style.css contained an unscoped `legend { color: ... }` rule, so the
plugin silently restyled the legend of every fieldset in Redmine --
including core pages and whatever the active theme intended
Restrict the hook to the four controllers whose views need the assets,
and scope the legend rule to `.controller-workloads`. `#year-nav` is an
id used only by this plugin's holiday view and is left as is.
The plugin settings page (`settings` controller) renders with core
classes only -- box, tabular, info -- and does not need style.css.
---
assets/stylesheets/style.css | 2 +-
init.rb | 21 ++++++++++++++++++++-
2 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/assets/stylesheets/style.css b/assets/stylesheets/style.css
index fc8b40b..61504d8 100644
--- a/assets/stylesheets/style.css
+++ b/assets/stylesheets/style.css
@@ -36,7 +36,7 @@
display: inline-block;
}
-legend {
+.controller-workloads legend {
color: var(--anthracite);
}
diff --git a/init.rb b/init.rb
index 31f8df1..6958936 100755
--- a/init.rb
+++ b/init.rb
@@ -46,8 +46,27 @@
end
class RedmineToolbarHookListener < Redmine::Hook::ViewListener
- def view_layouts_base_html_head(_context)
+ # Controllers whose views need the plugin's assets.
+ WORKLOAD_CONTROLLERS = %w[
+ workloads
+ wl_user_datas
+ wl_user_vacations
+ wl_national_holiday
+ ].freeze
+
+ def view_layouts_base_html_head(context = {})
+ return '' unless workload_page?(context)
+
javascript_include_tag('slides', plugin: :redmine_workload) +
stylesheet_link_tag('style', plugin: :redmine_workload)
end
+
+ private
+
+ def workload_page?(context)
+ controller = context[:controller]
+ return false unless controller
+
+ WORKLOAD_CONTROLLERS.include?(controller.params[:controller].to_s)
+ end
end