diff --git a/app/controllers/admin/custom_fields/custom_field_projects_controller.rb b/app/controllers/admin/custom_fields/custom_field_projects_controller.rb index e21966dd67b9..3ab0b132cb14 100644 --- a/app/controllers/admin/custom_fields/custom_field_projects_controller.rb +++ b/app/controllers/admin/custom_fields/custom_field_projects_controller.rb @@ -58,7 +58,7 @@ def new def create create_service = ::CustomFields::CustomFieldProjects::BulkCreateService - .new(user: current_user, projects: @projects, custom_field: @custom_field, + .new(user: current_user, projects: @projects, model: @custom_field, include_sub_projects: include_sub_projects?) .call diff --git a/app/controllers/admin/settings/project_custom_fields_controller.rb b/app/controllers/admin/settings/project_custom_fields_controller.rb index 5bccb8501fc7..6b9015a381a9 100644 --- a/app/controllers/admin/settings/project_custom_fields_controller.rb +++ b/app/controllers/admin/settings/project_custom_fields_controller.rb @@ -82,7 +82,7 @@ def new_link def link create_service = ProjectCustomFieldProjectMappings::BulkCreateService - .new(user: current_user, projects: @projects, project_custom_field: @custom_field, + .new(user: current_user, projects: @projects, model: @custom_field, include_sub_projects: include_sub_projects?) .call diff --git a/app/services/custom_fields/custom_field_projects/bulk_create_service.rb b/app/services/custom_fields/custom_field_projects/bulk_create_service.rb index cfa9673cc6ae..708c3041f2c4 100644 --- a/app/services/custom_fields/custom_field_projects/bulk_create_service.rb +++ b/app/services/custom_fields/custom_field_projects/bulk_create_service.rb @@ -31,10 +31,6 @@ module CustomFields module CustomFieldProjects class BulkCreateService < ::BulkServices::ProjectMappings::BaseCreateService - def initialize(user:, projects:, custom_field:, include_sub_projects: false) - super(user:, projects:, model: custom_field, include_sub_projects:) - end - def permission = :select_custom_fields def model_foreign_key_id = :custom_field_id def mapping_model_class = CustomFieldsProject diff --git a/app/services/project_custom_field_project_mappings/bulk_create_service.rb b/app/services/project_custom_field_project_mappings/bulk_create_service.rb index 3bc34b44a142..ffa3e76465a6 100644 --- a/app/services/project_custom_field_project_mappings/bulk_create_service.rb +++ b/app/services/project_custom_field_project_mappings/bulk_create_service.rb @@ -30,10 +30,6 @@ module ProjectCustomFieldProjectMappings class BulkCreateService < ::BulkServices::ProjectMappings::BaseCreateService - def initialize(user:, projects:, project_custom_field:, include_sub_projects: false) - super(user:, projects:, model: project_custom_field, include_sub_projects:) - end - private def permission = :select_project_custom_fields diff --git a/spec/services/bulk_services/project_mappings/behaves_like_bulk_project_mapping_create_service.rb b/spec/services/bulk_services/project_mappings/behaves_like_bulk_project_mapping_create_service.rb new file mode 100644 index 000000000000..0784ae51dc3c --- /dev/null +++ b/spec/services/bulk_services/project_mappings/behaves_like_bulk_project_mapping_create_service.rb @@ -0,0 +1,172 @@ +#-- 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" + +RSpec.shared_examples "BulkServices project mappings create service" do + let(:model_mapping_class) { raise("Please define the model mapping class") } + let(:model_foreign_key_id) { raise("Please define the model foreign key id") } + let(:required_permission) { raise("Please define the required permission for the bulk create action") } + let(:model) { raise("Please define the model that will be linked to projects") } + + context "with admin permissions" do + let(:user) { create(:admin) } + + context "with a single project" do + let(:project) { create(:project) } + let(:instance) { described_class.new(user:, projects: [project], model:) } + + it "creates the mappings" do + expect { instance.call }.to change(model_mapping_class, :count).by(1) + + aggregate_failures "creates the mapping for the correct project and custom field" do + expect(model_mapping_class.where(model_foreign_key_id => model.id).pluck(:project_id)).to contain_exactly(project.id) + end + end + end + + context "with subprojects" do + let(:projects) { create_list(:project, 2) } + let!(:subproject) { create(:project, parent: projects.first) } + let!(:subproject2) { create(:project, parent: subproject) } + + it "creates the mappings for the project and sub-projects" do + create_service = described_class.new(user:, projects: projects.map(&:reload), model:, + include_sub_projects: true) + + expect { create_service.call }.to change(model_mapping_class, :count).by(4) + + aggregate_failures "creates the mapping for the correct project and custom field" do + expect(model_mapping_class.where(model_foreign_key_id => model.id).pluck(:project_id)) + .to contain_exactly(*projects.map(&:id), subproject.id, subproject2.id) + end + end + end + + context "with multiple projects including subprojects" do + let(:project) { create(:project) } + let!(:subproject) { create(:project, parent: project) } + + it "creates the mappings for the project and sub-projects" do + create_service = described_class.new(user:, projects: [project.reload, subproject], model:, + include_sub_projects: true) + + expect { create_service.call }.to change(model_mapping_class, :count).by(2) + + aggregate_failures "creates the mapping for the correct project and custom field" do + expect(model_mapping_class.where(model_foreign_key_id => model.id).pluck(:project_id)) + .to contain_exactly(project.id, subproject.id) + end + end + end + + context "with duplicates" do + let(:project) { create(:project) } + let(:instance) { described_class.new(user:, projects: [project, project], model:) } + + it "creates the mappings only once" do + expect { instance.call }.to change(model_mapping_class, :count).by(1) + + aggregate_failures "creates the mapping for the correct project and custom field" do + expect(model_mapping_class.where(model_foreign_key_id => model.id).pluck(:project_id)) + .to contain_exactly(project.id) + end + end + end + end + + context "with non-admin but sufficient permissions" do + let(:user) do + create(:user, + member_with_permissions: { + project => %w[ + view_work_packages + edit_project + ].push(required_permission) + }) + end + + let(:project) { create(:project) } + let(:instance) { described_class.new(user:, projects: [project], model:) } + + it "creates the mappings" do + expect { instance.call }.to change(model_mapping_class, :count).by(1) + + aggregate_failures "creates the mapping for the correct project and custom field" do + expect(model_mapping_class.where(model_foreign_key_id => model.id).pluck(:project_id)).to contain_exactly(project.id) + end + end + end + + context "without sufficient permissions" do + let(:user) do + create(:user, + member_with_permissions: { + project => %w[ + view_work_packages + edit_project + ] + }) + end + let(:project) { create(:project) } + let(:instance) { described_class.new(user:, projects: [project], model:) } + + it "does not create the mappings" do + expect { instance.call }.not_to change(model_mapping_class, :count) + expect(instance.call).to be_failure + end + end + + context "with empty projects" do + let(:user) { create(:admin) } + let(:instance) { described_class.new(user:, projects: [], model:) } + + it "does not create the mappings" do + service_result = instance.call + expect(service_result).to be_failure + expect(service_result.errors).to eq("not found") + end + end + + context "with archived projects" do + let(:user) { create(:admin) } + let(:archived_project) { create(:project, active: false) } + let(:active_project) { create(:project) } + + let(:instance) { described_class.new(user:, projects: [archived_project, active_project], model:) } + + it "only creates mappins for the active project" do + expect { instance.call }.to change(model_mapping_class, :count).by(1) + + aggregate_failures "creates the mapping for the correct project and custom field" do + expect(model_mapping_class.where(model_foreign_key_id => model.id).pluck(:project_id)) + .to contain_exactly(active_project.id) + end + end + end +end diff --git a/spec/services/custom_fields/custom_field_projects/bulk_create_service_spec.rb b/spec/services/custom_fields/custom_field_projects/bulk_create_service_spec.rb index 470f08a82289..354c1be32b59 100644 --- a/spec/services/custom_fields/custom_field_projects/bulk_create_service_spec.rb +++ b/spec/services/custom_fields/custom_field_projects/bulk_create_service_spec.rb @@ -27,143 +27,15 @@ #++ require "spec_helper" +require_relative "../../bulk_services/project_mappings/behaves_like_bulk_project_mapping_create_service" RSpec.describe CustomFields::CustomFieldProjects::BulkCreateService do shared_let(:custom_field) { create(:wp_custom_field) } - context "with admin permissions" do - let(:user) { create(:admin) } - - context "with a single project" do - let(:project) { create(:project) } - let(:instance) { described_class.new(user:, projects: [project], custom_field:) } - - it "creates the mappings" do - expect { instance.call }.to change(CustomFieldsProject, :count).by(1) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(CustomFieldsProject.where(custom_field:).pluck(:project_id)).to contain_exactly(project.id) - end - end - end - - context "with subprojects" do - let(:projects) { create_list(:project, 2) } - let!(:subproject) { create(:project, parent: projects.first) } - let!(:subproject2) { create(:project, parent: subproject) } - - it "creates the mappings for the project and sub-projects" do - create_service = described_class.new(user:, projects: projects.map(&:reload), custom_field:, - include_sub_projects: true) - - expect { create_service.call }.to change(CustomFieldsProject, :count).by(4) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(CustomFieldsProject.where(custom_field:).pluck(:project_id)) - .to contain_exactly(*projects.map(&:id), subproject.id, subproject2.id) - end - end - end - - context "with multiple projects including subprojects" do - let(:project) { create(:project) } - let!(:subproject) { create(:project, parent: project) } - - it "creates the mappings for the project and sub-projects" do - create_service = described_class.new(user:, projects: [project.reload, subproject], custom_field:, - include_sub_projects: true) - - expect { create_service.call }.to change(CustomFieldsProject, :count).by(2) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(CustomFieldsProject.where(custom_field:).pluck(:project_id)) - .to contain_exactly(project.id, subproject.id) - end - end - end - - context "with duplicates" do - let(:project) { create(:project) } - let(:instance) { described_class.new(user:, projects: [project, project], custom_field:) } - - it "creates the mappings only once" do - expect { instance.call }.to change(CustomFieldsProject, :count).by(1) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(CustomFieldsProject.where(custom_field:).pluck(:project_id)).to contain_exactly(project.id) - end - end - end - end - - context "with non-admin but sufficient permissions" do - let(:user) do - create(:user, - member_with_permissions: { - project => %w[ - view_work_packages - edit_project - select_custom_fields - ] - }) - end - - let(:project) { create(:project) } - let(:instance) { described_class.new(user:, projects: [project], custom_field:) } - - it "creates the mappings" do - expect { instance.call }.to change(CustomFieldsProject, :count).by(1) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(CustomFieldsProject.where(custom_field:).pluck(:project_id)).to contain_exactly(project.id) - end - end - end - - context "without sufficient permissions" do - let(:user) do - create(:user, - member_with_permissions: { - project => %w[ - view_work_packages - edit_project - ] - }) - end - let(:project) { create(:project) } - let(:instance) { described_class.new(user:, projects: [project], custom_field:) } - - it "does not create the mappings" do - expect { instance.call }.not_to change(CustomFieldsProject, :count) - expect(instance.call).to be_failure - end - end - - context "with empty projects" do - let(:user) { create(:admin) } - let(:instance) { described_class.new(user:, projects: [], custom_field:) } - - it "does not create the mappings" do - service_result = instance.call - expect(service_result).to be_failure - expect(service_result.errors).to eq("not found") - end - end - - context "with archived projects" do - let(:user) { create(:admin) } - let(:archived_project) { create(:project, active: false) } - let(:active_project) { create(:project) } - - let(:instance) { described_class.new(user:, projects: [archived_project, active_project], custom_field:) } - - it "only creates mappins for the active project" do - expect { instance.call }.to change(CustomFieldsProject, :count).by(1) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(CustomFieldsProject.where(custom_field:).pluck(:project_id)) - .to contain_exactly(active_project.id) - end - end + it_behaves_like "BulkServices project mappings create service" do + let(:model) { custom_field } + let(:model_mapping_class) { CustomFieldsProject } + let(:model_foreign_key_id) { :custom_field_id } + let(:required_permission) { :select_custom_fields } end end diff --git a/spec/services/project_custom_field_project_mappings/bulk_create_service_spec.rb b/spec/services/project_custom_field_project_mappings/bulk_create_service_spec.rb index 00012cfaaf59..ab6ea95bad07 100644 --- a/spec/services/project_custom_field_project_mappings/bulk_create_service_spec.rb +++ b/spec/services/project_custom_field_project_mappings/bulk_create_service_spec.rb @@ -27,146 +27,15 @@ #++ require "spec_helper" +require_relative "../bulk_services/project_mappings/behaves_like_bulk_project_mapping_create_service" RSpec.describe ProjectCustomFieldProjectMappings::BulkCreateService do shared_let(:project_custom_field) { create(:project_custom_field) } - context "with admin permissions" do - let(:user) { create(:admin) } - - context "with a single project" do - let(:project) { create(:project) } - let(:instance) { described_class.new(user:, projects: [project], project_custom_field:) } - - it "creates the mappings" do - expect { instance.call }.to change(ProjectCustomFieldProjectMapping, :count).by(1) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(ProjectCustomFieldProjectMapping.last.project).to eq(project) - expect(ProjectCustomFieldProjectMapping.last.project_custom_field).to eq(project_custom_field) - end - end - end - - context "with subprojects" do - let(:projects) { create_list(:project, 2) } - let!(:subproject) { create(:project, parent: projects.first) } - let!(:subproject2) { create(:project, parent: subproject) } - - it "creates the mappings for the project and sub-projects" do - create_service = described_class.new(user:, projects: projects.map(&:reload), project_custom_field:, - include_sub_projects: true) - - expect { create_service.call }.to change(ProjectCustomFieldProjectMapping, :count).by(4) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(ProjectCustomFieldProjectMapping.where(project_custom_field:).pluck(:project_id)) - .to contain_exactly(*projects.map(&:id), subproject.id, subproject2.id) - end - end - end - - context "with multiple projects including subprojects" do - let(:project) { create(:project) } - let!(:subproject) { create(:project, parent: project) } - - it "creates the mappings for the project and sub-projects" do - create_service = described_class.new(user:, projects: [project.reload, subproject], project_custom_field:, - include_sub_projects: true) - - expect { create_service.call }.to change(ProjectCustomFieldProjectMapping, :count).by(2) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(ProjectCustomFieldProjectMapping.where(project_custom_field:).pluck(:project_id)) - .to contain_exactly(project.id, subproject.id) - end - end - end - - context "with duplicates" do - let(:project) { create(:project) } - let(:instance) { described_class.new(user:, projects: [project, project], project_custom_field:) } - - it "creates the mappings only once" do - expect { instance.call }.to change(ProjectCustomFieldProjectMapping, :count).by(1) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(ProjectCustomFieldProjectMapping.last.project).to eq(project) - expect(ProjectCustomFieldProjectMapping.last.project_custom_field).to eq(project_custom_field) - end - end - end - end - - context "with non-admin but sufficient permissions" do - let(:user) do - create(:user, - member_with_permissions: { - project => %w[ - view_work_packages - edit_project - select_project_custom_fields - ] - }) - end - - let(:project) { create(:project) } - let(:instance) { described_class.new(user:, projects: [project], project_custom_field:) } - - it "creates the mappings" do - expect { instance.call }.to change(ProjectCustomFieldProjectMapping, :count).by(1) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(ProjectCustomFieldProjectMapping.last.project).to eq(project) - expect(ProjectCustomFieldProjectMapping.last.project_custom_field).to eq(project_custom_field) - end - end - end - - context "without sufficient permissions" do - let(:user) do - create(:user, - member_with_permissions: { - project => %w[ - view_work_packages - edit_project - ] - }) - end - let(:project) { create(:project) } - let(:instance) { described_class.new(user:, projects: [project], project_custom_field:) } - - it "does not create the mappings" do - expect { instance.call }.not_to change(ProjectCustomFieldProjectMapping, :count) - expect(instance.call).to be_failure - end - end - - context "with empty projects" do - let(:user) { create(:admin) } - let(:instance) { described_class.new(user:, projects: [], project_custom_field:) } - - it "does not create the mappings" do - service_result = instance.call - expect(service_result).to be_failure - expect(service_result.errors).to eq("not found") - end - end - - context "with archived projects" do - let(:user) { create(:admin) } - let(:archived_project) { create(:project, active: false) } - let(:active_project) { create(:project) } - - let(:instance) { described_class.new(user:, projects: [archived_project, active_project], project_custom_field:) } - - it "only creates mappins for the active project" do - expect { instance.call }.to change(ProjectCustomFieldProjectMapping, :count).by(1) - - aggregate_failures "creates the mapping for the correct project and custom field" do - expect(ProjectCustomFieldProjectMapping.where(project_custom_field:).pluck(:project_id)) - .to contain_exactly(active_project.id) - end - end + it_behaves_like "BulkServices project mappings create service" do + let(:model) { project_custom_field } + let(:model_mapping_class) { ProjectCustomFieldProjectMapping } + let(:model_foreign_key_id) { :custom_field_id } + let(:required_permission) { :select_project_custom_fields } end end