From 7ea5acd357a935f2c9820f1ee940297c5606e030 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Fri, 6 Sep 2024 12:52:30 +0300 Subject: [PATCH] [Op#57662]: Trigger AMPF Sync Job when folder mode is switched from/to "automatic" mode (#16637) https://community.openproject.org/wp/57662 --- .../project_storages/bulk_create_service.rb | 5 +- .../project_storages/create_service.rb | 6 +- .../project_storages/delete_service.rb | 6 +- .../project_storages/notifications_service.rb | 57 ++++++++++ .../project_storages/update_service.rb | 6 +- .../lib/open_project/storages/engine.rb | 2 +- .../bulk_create_service_spec.rb | 21 ++-- .../notifications_service_spec.rb | 101 ++++++++++++++++++ .../shared_event_gun_examples.rb | 5 +- 9 files changed, 184 insertions(+), 25 deletions(-) create mode 100644 modules/storages/app/services/storages/project_storages/notifications_service.rb create mode 100644 modules/storages/spec/services/storages/project_storages/notifications_service_spec.rb diff --git a/modules/storages/app/services/storages/project_storages/bulk_create_service.rb b/modules/storages/app/services/storages/project_storages/bulk_create_service.rb index 3b392009c488..23ab331e4d7e 100644 --- a/modules/storages/app/services/storages/project_storages/bulk_create_service.rb +++ b/modules/storages/app/services/storages/project_storages/bulk_create_service.rb @@ -99,9 +99,10 @@ def create_last_project_folders(service_call, params) end def broadcast_project_storages_created(params) - OpenProject::Notifications.send( - OpenProject::Events::PROJECT_STORAGE_CREATED, + ::Storages::ProjectStorages::NotificationsService.broadcast_raw( + event: :created, project_folder_mode: params[:project_folder_mode], + project_folder_mode_previously_was: nil, storage: @storage ) end diff --git a/modules/storages/app/services/storages/project_storages/create_service.rb b/modules/storages/app/services/storages/project_storages/create_service.rb index b501ae4716af..fae039e12e14 100644 --- a/modules/storages/app/services/storages/project_storages/create_service.rb +++ b/modules/storages/app/services/storages/project_storages/create_service.rb @@ -37,11 +37,7 @@ def after_perform(service_call) project_storage = service_call.result project_folder_mode = project_storage.project_folder_mode.to_sym add_historical_data(service_call) if project_folder_mode != :inactive - OpenProject::Notifications.send( - OpenProject::Events::PROJECT_STORAGE_CREATED, - project_folder_mode:, - storage: project_storage.storage - ) + ::Storages::ProjectStorages::NotificationsService.broadcast_project_storage_created(project_storage:) service_call end diff --git a/modules/storages/app/services/storages/project_storages/delete_service.rb b/modules/storages/app/services/storages/project_storages/delete_service.rb index f521c7a7fd9b..b7608229b900 100644 --- a/modules/storages/app/services/storages/project_storages/delete_service.rb +++ b/modules/storages/app/services/storages/project_storages/delete_service.rb @@ -48,10 +48,8 @@ def persist(service_result) super.tap do |deletion_result| if deletion_result.success? delete_associated_file_links - OpenProject::Notifications.send( - OpenProject::Events::PROJECT_STORAGE_DESTROYED, - project_folder_mode: deletion_result.result.project_folder_mode.to_sym, - storage: deletion_result.result.storage + ::Storages::ProjectStorages::NotificationsService.broadcast_project_storage_destroyed( + project_storage: deletion_result.result ) end end diff --git a/modules/storages/app/services/storages/project_storages/notifications_service.rb b/modules/storages/app/services/storages/project_storages/notifications_service.rb new file mode 100644 index 000000000000..bf625e7d1255 --- /dev/null +++ b/modules/storages/app/services/storages/project_storages/notifications_service.rb @@ -0,0 +1,57 @@ +#-- 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 Storages::ProjectStorages::NotificationsService + module_function + + %i[created updated destroyed].each do |event| + define_method :"broadcast_project_storage_#{event}" do |project_storage:| + broadcast(event:, project_storage:) + end + end + + def broadcast(event:, project_storage:) + broadcast_raw event:, project_folder_mode: project_storage.project_folder_mode.to_sym, + project_folder_mode_previously_was: project_storage.project_folder_mode_previously_was&.to_sym, + storage: project_storage.storage + end + + def broadcast_raw(event:, project_folder_mode:, project_folder_mode_previously_was:, storage:) + OpenProject::Notifications.send( + "OpenProject::Events::PROJECT_STORAGE_#{event.to_s.upcase}".constantize, + project_folder_mode:, + project_folder_mode_previously_was:, + storage: + ) + end + + def automatic_folder_mode_broadcast?(broadcasted_payload) + folder_modes = broadcasted_payload.values_at(:project_folder_mode, :project_folder_mode_previously_was).compact + folder_modes.map { |mode| mode&.to_sym }.any?(:automatic) + end +end diff --git a/modules/storages/app/services/storages/project_storages/update_service.rb b/modules/storages/app/services/storages/project_storages/update_service.rb index 9bc15dac1e51..b954d7ab9eed 100644 --- a/modules/storages/app/services/storages/project_storages/update_service.rb +++ b/modules/storages/app/services/storages/project_storages/update_service.rb @@ -38,11 +38,7 @@ def after_perform(service_call) project_storage = service_call.result project_folder_mode = project_storage.project_folder_mode.to_sym add_historical_data(service_call) if project_folder_mode != :inactive - OpenProject::Notifications.send( - OpenProject::Events::PROJECT_STORAGE_UPDATED, - project_folder_mode:, - storage: project_storage.storage - ) + ::Storages::ProjectStorages::NotificationsService.broadcast_project_storage_updated(project_storage:) service_call end diff --git a/modules/storages/lib/open_project/storages/engine.rb b/modules/storages/lib/open_project/storages/engine.rb index d377ec2ffc5b..e1752ad5238e 100644 --- a/modules/storages/lib/open_project/storages/engine.rb +++ b/modules/storages/lib/open_project/storages/engine.rb @@ -117,7 +117,7 @@ def self.permissions OpenProject::Events::PROJECT_STORAGE_DESTROYED ].each do |event| OpenProject::Notifications.subscribe(event) do |payload| - if payload[:project_folder_mode]&.to_sym == :automatic + if ::Storages::ProjectStorages::NotificationsService.automatic_folder_mode_broadcast?(payload) ::Storages::AutomaticallyManagedStorageSyncJob.debounce(payload[:storage]) ::Storages::ManageStorageIntegrationsJob.disable_cron_job_if_needed end diff --git a/modules/storages/spec/services/storages/project_storages/bulk_create_service_spec.rb b/modules/storages/spec/services/storages/project_storages/bulk_create_service_spec.rb index e2f141b5c861..ce36f350d4f3 100644 --- a/modules/storages/spec/services/storages/project_storages/bulk_create_service_spec.rb +++ b/modules/storages/spec/services/storages/project_storages/bulk_create_service_spec.rb @@ -56,7 +56,8 @@ aggregate_failures "broadcasts projects storages created event" do expect(OpenProject::Notifications).to have_received(:send) - .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, storage:) + .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, + project_folder_mode_previously_was: nil, storage:) end end end @@ -79,7 +80,8 @@ aggregate_failures "broadcasts projects storages created event" do expect(OpenProject::Notifications).to have_received(:send) - .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, storage:) + .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, + project_folder_mode_previously_was: nil, storage:) end end end @@ -101,7 +103,8 @@ aggregate_failures "broadcasts projects storages created event" do expect(OpenProject::Notifications).to have_received(:send) - .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, storage:) + .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, + project_folder_mode_previously_was: nil, storage:) end end end @@ -122,7 +125,8 @@ aggregate_failures "broadcasts projects storages created event" do expect(OpenProject::Notifications).to have_received(:send) - .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, storage:) + .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, + project_folder_mode_previously_was: nil, storage:) end end end @@ -153,7 +157,8 @@ aggregate_failures "broadcasts projects storages created event" do expect(OpenProject::Notifications).to have_received(:send) - .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, storage:) + .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, + project_folder_mode_previously_was: nil, storage:) end end end @@ -174,7 +179,8 @@ expect { instance.call(project_folder_mode:) }.not_to change(Storages::ProjectStorage, :count) expect(instance.call).to be_failure expect(OpenProject::Notifications).not_to have_received(:send) - .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, storage:) + .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, + project_folder_mode_previously_was: nil, storage:) end end @@ -205,7 +211,8 @@ aggregate_failures "broadcasts projects storages created event" do expect(OpenProject::Notifications).to have_received(:send) - .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, storage:) + .with(OpenProject::Events::PROJECT_STORAGE_CREATED, project_folder_mode:, + project_folder_mode_previously_was: nil, storage:) end end end diff --git a/modules/storages/spec/services/storages/project_storages/notifications_service_spec.rb b/modules/storages/spec/services/storages/project_storages/notifications_service_spec.rb new file mode 100644 index 000000000000..7268a45188d4 --- /dev/null +++ b/modules/storages/spec/services/storages/project_storages/notifications_service_spec.rb @@ -0,0 +1,101 @@ +#-- 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. +#++ + +require "spec_helper" +require_module_spec_helper + +RSpec.describe Storages::ProjectStorages::NotificationsService do + shared_let(:project_storage) { create(:project_storage, :as_automatically_managed) } + + before do + allow(OpenProject::Notifications).to receive(:send) + end + + shared_examples "broadcasts the project storage event" do |event| + it "broadcasts the project storage event #{event}" do + expect(OpenProject::Notifications).to have_received(:send) + .with(event, project_folder_mode: project_storage.project_folder_mode.to_sym, + project_folder_mode_previously_was: project_storage.project_folder_mode_previously_was&.to_sym, + storage: project_storage.storage) + end + end + + %i[created destroyed].each do |event| + describe ".broadcast_project_storage_#{event}" do + before { described_class.public_send(:"broadcast_project_storage_#{event}", project_storage:) } + + it_behaves_like "broadcasts the project storage event", + OpenProject::Events.const_get("PROJECT_STORAGE_#{event.to_s.upcase}") + end + end + + describe ".broadcast_project_storage_updated" do + before do + project_storage.update(project_folder_mode: "inactive") + described_class.broadcast_project_storage_updated(project_storage:) + end + + after { project_storage.update(project_folder_mode: :automatic) } + + it "broadcasts the project storage event" do + expect(OpenProject::Notifications).to have_received(:send) + .with(OpenProject::Events::PROJECT_STORAGE_UPDATED, + project_folder_mode: :inactive, + project_folder_mode_previously_was: :automatic, + storage: project_storage.storage) + end + end + + describe ".automatic_folder_mode_broadcast?" do + subject { described_class.automatic_folder_mode_broadcast?(broadcasted_payload) } + + context "when project_folder_mode is automatic" do + let(:broadcasted_payload) { { project_folder_mode: "automatic" } } + + it { is_expected.to be(true) } + end + + context "when project_folder_mode_previously_was is automatic" do + let(:broadcasted_payload) { { project_folder_mode_previously_was: "automatic" } } + + it { is_expected.to be(true) } + end + + context "when only one of project_folder_mode and project_folder_mode_previously_was is automatic" do + let(:broadcasted_payload) { { project_folder_mode: "inactive", project_folder_mode_previously_was: "automatic" } } + + it { is_expected.to be(true) } + end + + context "when both project_folder_mode and project_folder_mode_previously_was are not automatic" do + let(:broadcasted_payload) { { project_folder_mode: "inactive", project_folder_mode_previously_was: "inactive" } } + + it { is_expected.to be(false) } + end + end +end diff --git a/modules/storages/spec/services/storages/project_storages/shared_event_gun_examples.rb b/modules/storages/spec/services/storages/project_storages/shared_event_gun_examples.rb index 6cf56701cf18..22622725b9df 100644 --- a/modules/storages/spec/services/storages/project_storages/shared_event_gun_examples.rb +++ b/modules/storages/spec/services/storages/project_storages/shared_event_gun_examples.rb @@ -41,7 +41,10 @@ subject expect(OpenProject::Notifications).to( - have_received(:send).with(event, project_folder_mode: mode, storage: model_instance.storage) + have_received(:send) + .with(event, project_folder_mode: mode, + project_folder_mode_previously_was: model_instance.project_folder_mode_previously_was, + storage: model_instance.storage) ) end end