diff --git a/app/services/projects/create_artifact_work_package_service.rb b/app/services/projects/create_artifact_work_package_service.rb index a156d95444e..eeeabacdf74 100644 --- a/app/services/projects/create_artifact_work_package_service.rb +++ b/app/services/projects/create_artifact_work_package_service.rb @@ -58,6 +58,44 @@ module Projects service_call end + def after_perform(service_call) + return service_call if store_attachment_locally? + + if project_storage.nil? + service_call.errors.add(:base, I18n.t("projects.wizard.create_artifact_storage_error")) + return service_call + end + + upload_artifact_to_storage(service_call) + end + + def upload_artifact_to_storage(service_call) + export = create_pdf_export! + + storage_call = Storages::UploadFileService + .call( + container: service_call.result, + project_storage:, + file_path: project.project_creation_wizard_artifact_name, + file_data: StringIO.new(export.content), + filename: export.title + ) + + storage_call.on_failure do + service_call.merge!(storage_call, without_success: true) + end + + service_call + end + + def project_storage + return @project_storage if defined?(@project_storage) + + @project_storage = project + .project_storages + .find_by(id: project.project_creation_wizard_artifact_export_storage) + end + def create_artifact_work_package create_params = { project:, @@ -65,10 +103,10 @@ module Projects status_id: project.project_creation_wizard_status_when_submitted_id, subject:, assigned_to_id:, - attachments: [pdf_attachment], journal_notes: } + create_params[:attachments] = [pdf_attachment] if store_attachment_locally? WorkPackages::CreateService.new(user:).call(create_params) end @@ -86,6 +124,10 @@ module Projects scope: "settings.project_initiation_request.name.options") end + def store_attachment_locally? + project.project_creation_wizard_artifact_export_type == "attachment" + end + def assigned_to_id project.custom_value_for(assignee_custom_field).value end @@ -101,9 +143,12 @@ module Projects .find_by(id: project.project_creation_wizard_assignee_custom_field_id) end - def pdf_attachment - export = Project::PDFExport::ProjectInitiation.new(project).export! + def create_pdf_export! + Project::PDFExport::ProjectInitiation.new(project).export! + end + def pdf_attachment + export = create_pdf_export! file = OpenProject::Files.create_uploaded_file( name: export.title, content_type: export.mime_type, diff --git a/config/locales/en.yml b/config/locales/en.yml index adfdb1aa834..e0a653cc29a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -655,6 +655,7 @@ en: success: "Project attributes saved and artifact work package created successfully." progress_label: "%{current} of %{total}" create_artifact_work_package_error: "Failed to create artifact work package" + create_artifact_storage_error: "Failed to store artifact in file storage" lists: create: success: "The modified list has been saved as a new list" diff --git a/spec/services/projects/create_artifact_work_package_service_spec.rb b/spec/services/projects/create_artifact_work_package_service_spec.rb index 91be042a125..617837f82b2 100644 --- a/spec/services/projects/create_artifact_work_package_service_spec.rb +++ b/spec/services/projects/create_artifact_work_package_service_spec.rb @@ -47,6 +47,7 @@ RSpec.describe Projects::CreateArtifactWorkPackageService do shared_let(:project) do create( :project, + name: "Important Project", types: [type], project_custom_fields: [user_custom_field], # project initiation request settings @@ -103,18 +104,6 @@ RSpec.describe Projects::CreateArtifactWorkPackageService do expect(artifact_work_package.assigned_to).to eq(assignee_user) end - it "attaches the project initiation request pdf file to the artifact work package" do - result = instance.call - project = result.result - - artifact_work_package = WorkPackage.find(project.project_creation_wizard_artifact_work_package_id) - expect(artifact_work_package.attachments.count).to eq(1) - - attachment = artifact_work_package.attachments.first - expect(attachment.file.content_type).to eq("application/pdf") - expect(attachment.author).to eq(current_user) - end - it "sets the subject to the artifact name configured in the project initiation request settings" do result = instance.call project = result.result @@ -156,5 +145,79 @@ RSpec.describe Projects::CreateArtifactWorkPackageService do expect(artifact_work_package.last_journal.notes).to include(project.project_creation_wizard_work_package_comment) expect(artifact_work_package.last_journal.notes).to include(/]+>@#{assignee_user.name}<\/mention>/) end + + context "when artifact storage is internal" do + it "attaches directly to the work package" do + project.update(project_creation_wizard_artifact_export_type: "attachment") + result = instance.call + project = result.result + + artifact_work_package = WorkPackage.find(project.project_creation_wizard_artifact_work_package_id) + expect(artifact_work_package.attachments.count).to eq(1) + attachment = artifact_work_package.attachments.first + date = Date.current.iso8601 + expect(attachment.content_type).to eq "application/pdf" + expect(attachment.filename).to match /Important_Project_Project_mandate_#{date}_\d+-\d+.pdf/ + end + end + + context "when artifact storage is project storage" do + let(:storage) { create(:nextcloud_storage_with_local_connection) } + let(:project_storage) { create(:project_storage, project:, storage:, project_folder_id: "/project_folder") } + + let(:service_result) { ServiceResult.success(result: nil) } + + before do + project.update( + project_creation_wizard_artifact_export_type: "file_link", + project_creation_wizard_artifact_export_storage: project_storage.id + ) + + allow(Storages::UploadFileService) + .to receive(:call) + .and_return(service_result) + end + + it "calls the nextcloud storage service" do + result = instance.call + project = result.result + + expect(result).to be_success + artifact_work_package = WorkPackage.find(project.project_creation_wizard_artifact_work_package_id) + expect(artifact_work_package.attachments.count).to eq(0) + + date = Date.current.iso8601 + expect(Storages::UploadFileService) + .to have_received(:call) + .with(container: project, + project_storage:, + file_path: "project_mandate", + filename: /Important_Project_Project_mandate_#{date}_\d+-\d+.pdf/, + file_data: instance_of(StringIO)) + end + + context "when service call fails" do + let(:service_result) do + ServiceResult.failure(result: nil).tap do |result| + result.errors.add(:base, "Something happened!") + end + end + + it "keeps the work package, but shows an error" do + result = instance.call + project = result.result + + expect(Storages::UploadFileService) + .to have_received(:call) + + # The outer service is successful, but an error is added + expect(result).to be_success + expect(result.errors[:base]).to include "Something happened!" + + artifact_work_package = WorkPackage.find(project.project_creation_wizard_artifact_work_package_id) + expect(artifact_work_package.attachments.count).to eq(0) + end + end + end end end