[#71305] Introduce MODULE_ENABLED event and use it for backlogs

This commit is contained in:
Tobias Dillmann
2026-05-19 13:31:59 +02:00
parent f1568c2e29
commit 0537f52f4b
7 changed files with 68 additions and 65 deletions
+2
View File
@@ -54,6 +54,8 @@ class EnabledModule < ApplicationRecord
create_managed_repository
end
end
OpenProject::Notifications.send(OpenProject::Events::MODULE_ENABLED, enabled_module: self)
end
def create_managed_repository
+1
View File
@@ -80,6 +80,7 @@ module OpenProject
WATCHER_ADDED = "watcher_added"
WATCHER_DESTROYED = "watcher_destroyed"
MODULE_ENABLED = "module_enabled"
MODULE_DISABLED = "module_disabled"
WORK_PACKAGE_SHARED = "work_package_shared"
@@ -130,8 +130,7 @@ module OpenProject::Backlogs
patches %i[PermittedParams
WorkPackage
Project
EnabledModule]
Project]
patch_with_namespace :BasicData, :SettingSeeder
patch_with_namespace :Projects, :CopyService
@@ -193,6 +192,29 @@ module OpenProject::Backlogs
initializer "openproject_backlogs.event_subscriptions" do
Rails.application.config.after_initialize do
# When the backlogs module is first enabled on a project, automatically populate
# the project's done_statuses with all statuses that are globally marked as closed
# (is_closed: true). This mirrors the form behavior where these statuses are
# pre-selected and disabled, so users never have to visit the settings page just
# to get sensible defaults.
OpenProject::Notifications.subscribe(OpenProject::Events::MODULE_ENABLED) do |payload|
enabled_module = payload[:enabled_module]
next unless enabled_module.name == "backlogs"
project = enabled_module.project
next unless project
mandatory_ids = Status.where(is_closed: true).pluck(:id)
next if mandatory_ids.empty?
merged_ids = project.done_statuses.reorder(nil).pluck(:id) | mandatory_ids
project.class.transaction do
project.done_statuses = [] # explicit clearing is necessary due to HABTM cache behavior
project.done_statuses = Status.where(id: merged_ids)
end
end
OpenProject::Notifications.subscribe(OpenProject::Events::MODULE_DISABLED) do |payload|
disabled_module = payload[:disabled_module]
next unless disabled_module.name == "backlogs"
@@ -1,59 +0,0 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject::Backlogs::Patches::EnabledModulePatch
extend ActiveSupport::Concern
included do
after_create :seed_backlogs_done_statuses, if: -> { name == "backlogs" }
end
private
# When the backlogs module is first enabled on a project, automatically populate
# the project's done_statuses with all statuses that are globally marked as closed
# (is_closed: true). This mirrors the form behavior where these statuses are
# pre-selected and disabled, so users never have to visit the settings page just
# to get sensible defaults.
def seed_backlogs_done_statuses # rubocop:disable Metrics/AbcSize
return unless project
mandatory_ids = Status.where(is_closed: true).pluck(:id)
return if mandatory_ids.empty?
merged_ids = (project.done_statuses.reorder(nil).pluck(:id) | mandatory_ids)
# Normalize the HABTM association explicitly by clearing and setting it:
project.class.transaction do
project.done_statuses = [] # This explicit clearing is necessary for some weird reason
project.done_statuses = Status.where(id: merged_ids)
end
end
end
@@ -195,7 +195,7 @@ RSpec.describe "Start and finish sprints", :js do
before do
# closed_status is a lazy `let` created after the shared_let(:project), so
# it is not present when the backlogs module is first enabled on the project
# and therefore not auto-seeded by EnabledModulePatch. We add it manually here.
# and therefore not auto-seeded by the MODULE_ENABLED event. We add it manually here.
project.done_statuses << closed_status unless project.done_statuses.include?(closed_status)
end
@@ -30,12 +30,12 @@
require "spec_helper"
RSpec.describe OpenProject::Backlogs::Patches::EnabledModulePatch do # rubocop:disable RSpec/SpecFilePathFormat
RSpec.describe "Backlogs MODULE_ENABLED event" do # rubocop:disable RSpec/DescribeClass
let!(:closed_status1) { create(:status, is_closed: true) }
let!(:closed_status2) { create(:status, is_closed: true) }
let!(:open_status) { create(:status, is_closed: false) }
describe "seed_backlogs_done_statuses callback" do
describe "seeding done_statuses on backlogs module enable" do
context "when the backlogs module is enabled on a project" do
it "seeds all is_closed statuses as done_statuses for the project" do
project = create(:project, enabled_module_names: %w[backlogs work_package_tracking])
+38 -1
View File
@@ -34,6 +34,40 @@ RSpec.describe EnabledModule do
# Force reload, as association is not always(?) showing
let(:project) { create(:project, enabled_module_names: modules).reload }
describe "MODULE_ENABLED event" do
let(:modules) { [] }
it "fires the event when a module is added" do
project # force evaluation before the stub is set up
allow(OpenProject::Notifications)
.to receive(:send)
.with(OpenProject::Events::MODULE_ENABLED, enabled_module: anything)
project.enabled_module_names = ["wiki"]
expect(OpenProject::Notifications)
.to have_received(:send)
.with(OpenProject::Events::MODULE_ENABLED, enabled_module: anything)
.once
end
it "does not fire the event when removing a module" do
project.enabled_module_names = ["wiki"]
allow(OpenProject::Notifications).to receive(:send).and_call_original # Consume journal events
allow(OpenProject::Notifications)
.to receive(:send)
.with(OpenProject::Events::MODULE_ENABLED, enabled_module: anything)
project.enabled_module_names = []
expect(OpenProject::Notifications)
.not_to have_received(:send)
.with(OpenProject::Events::MODULE_ENABLED, enabled_module: anything)
end
end
describe "MODULE_DISABLED event" do
let(:modules) { %w[wiki] }
@@ -55,13 +89,16 @@ RSpec.describe EnabledModule do
it "does not fire the event when creating a module" do
project.enabled_module_names = []
allow(OpenProject::Notifications).to receive(:send).and_call_original
allow(OpenProject::Notifications)
.to receive(:send)
.with(OpenProject::Events::MODULE_DISABLED, disabled_module: anything)
project.enabled_module_names = ["wiki"]
expect(OpenProject::Notifications).not_to have_received(:send)
expect(OpenProject::Notifications)
.not_to have_received(:send)
.with(OpenProject::Events::MODULE_DISABLED, disabled_module: anything)
end
end