Merge pull request #21168 from opf/feature/68864/nextcloud-pdf-storage

Upload artifact in storage when configured
This commit is contained in:
Oliver Günther
2025-11-25 19:45:56 +01:00
committed by GitHub
3 changed files with 124 additions and 15 deletions
@@ -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,
+1
View File
@@ -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"
@@ -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(/<mention[^>]+>@#{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