diff --git a/modules/bim/app/representers/bim/bcf/api/v2_1/topics/single_representer.rb b/modules/bim/app/representers/bim/bcf/api/v2_1/topics/single_representer.rb index d6384f46072..21b27af59af 100644 --- a/modules/bim/app/representers/bim/bcf/api/v2_1/topics/single_representer.rb +++ b/modules/bim/app/representers/bim/bcf/api/v2_1/topics/single_representer.rb @@ -59,7 +59,7 @@ module Bim::Bcf::API::V2_1 property :reference_links, getter: ->(decorator:, **) { - [decorator.api_v3_paths.work_package(work_package.id)] + [decorator.api_v3_paths.work_package(work_package.display_id)] } property :title, diff --git a/modules/bim/app/services/bim/bcf/issues/create_service.rb b/modules/bim/app/services/bim/bcf/issues/create_service.rb index c1aede3af9b..a3a579e7904 100644 --- a/modules/bim/app/services/bim/bcf/issues/create_service.rb +++ b/modules/bim/app/services/bim/bcf/issues/create_service.rb @@ -57,7 +57,7 @@ module Bim::Bcf end def use_work_package(links:, params:) - work_package = WorkPackage.visible(user).find_by(id: work_package_id_from_links(links)) + work_package = WorkPackage.visible(user).find_by_display_id(work_package_identifier_from_links(links)) return work_package_not_found_result if work_package.nil? ::WorkPackages::UpdateService @@ -71,10 +71,10 @@ module Bim::Bcf .call(**params) end - def work_package_id_from_links(links) + def work_package_identifier_from_links(links) links .take(1) - .map { |link| link.split("/").last.to_i } + .map { |link| link.split("/").last } .first end diff --git a/modules/bim/lib/open_project/bim/engine.rb b/modules/bim/lib/open_project/bim/engine.rb index d33ae69a3f5..ee4c5c969c7 100644 --- a/modules/bim/lib/open_project/bim/engine.rb +++ b/modules/bim/lib/open_project/bim/engine.rb @@ -163,7 +163,7 @@ module OpenProject::Bim { href: bcf_v2_1_paths.topics(represented.project.identifier), title: "Convert to BCF", - payload: { reference_links: [api_v3_paths.work_package(represented.id)] }, + payload: { reference_links: [api_v3_paths.work_package(represented.display_id)] }, method: :post } end diff --git a/modules/bim/spec/api/v3/work_packages/work_package_representer_spec.rb b/modules/bim/spec/api/v3/work_packages/work_package_representer_spec.rb index adf8651b250..9a6910efc1c 100644 --- a/modules/bim/spec/api/v3/work_packages/work_package_representer_spec.rb +++ b/modules/bim/spec/api/v3/work_packages/work_package_representer_spec.rb @@ -150,6 +150,19 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter do .to be_json_eql({ reference_links: ["/api/v3/work_packages/#{work_package.id}"] }.to_json) .at_path("_links/convertBCF/payload") end + + context "with semantic work package identifiers", + with_settings: { work_packages_identifier: "semantic" } do + let(:work_package) do + build_stubbed(:work_package, bcf_issue: bcf_topic, identifier: "PROJ-7", sequence_number: 7) + end + + it "uses the semantic identifier in the payload" do + expect(subject) + .to be_json_eql({ reference_links: ["/api/v3/work_packages/PROJ-7"] }.to_json) + .at_path("_links/convertBCF/payload") + end + end end context "if a bcf issue is assigned" do diff --git a/modules/bim/spec/bcf/bcf_xml/issue_writer_spec.rb b/modules/bim/spec/bcf/bcf_xml/issue_writer_spec.rb index eed560183e8..c90f4b8dc8b 100644 --- a/modules/bim/spec/bcf/bcf_xml/issue_writer_spec.rb +++ b/modules/bim/spec/bcf/bcf_xml/issue_writer_spec.rb @@ -190,6 +190,19 @@ RSpec.describe OpenProject::Bim::BcfXml::IssueWriter do end end + context "with semantic work package identifiers", + with_settings: { work_packages_identifier: "semantic" } do + let(:project) { create(:project, identifier: "BIMPROJ") } + + it "writes the semantic identifier into the ReferenceLink" do + work_package.reload + + expect(work_package.display_id).to start_with "BIMPROJ-" + expect(subject.at("Topic/ReferenceLink").content) + .to end_with "/work_packages/#{work_package.display_id}" + end + end + context "when bcf_issue snapshot is false" do let(:vp_snapshot) { false } diff --git a/modules/bim/spec/representers/bcf/api/v2_1/topics/single_representer_rendering_spec.rb b/modules/bim/spec/representers/bcf/api/v2_1/topics/single_representer_rendering_spec.rb index e8c5e4e86b1..a56f41b43ab 100644 --- a/modules/bim/spec/representers/bcf/api/v2_1/topics/single_representer_rendering_spec.rb +++ b/modules/bim/spec/representers/bcf/api/v2_1/topics/single_representer_rendering_spec.rb @@ -124,6 +124,29 @@ RSpec.describe Bim::Bcf::API::V2_1::Topics::SingleRepresenter, "rendering" do let(:value) { [api_v3_paths.work_package(work_package.id)] } let(:path) { "reference_links" } end + + context "with semantic work package identifiers", + with_settings: { work_packages_identifier: "semantic" } do + let(:work_package) do + build_stubbed(:work_package, + identifier: "PROJ-42", + sequence_number: 42, + assigned_to: assignee, + due_date: Time.zone.today, + status:, + priority:, + type:).tap do |wp| + allow(wp) + .to receive(:journals) + .and_return(journals) + end + end + + it_behaves_like "attribute" do + let(:value) { [api_v3_paths.work_package("PROJ-42")] } + let(:path) { "reference_links" } + end + end end context "title" do diff --git a/modules/bim/spec/requests/api/bcf/v2_1/topics_api_spec.rb b/modules/bim/spec/requests/api/bcf/v2_1/topics_api_spec.rb index 8a1c1b16d69..20b9d240167 100644 --- a/modules/bim/spec/requests/api/bcf/v2_1/topics_api_spec.rb +++ b/modules/bim/spec/requests/api/bcf/v2_1/topics_api_spec.rb @@ -127,7 +127,7 @@ RSpec.describe "BCF 2.1 topics resource", content_type: :json do modified_author: current_user.mail, modified_date: work_package.updated_at.iso8601(3), reference_links: [ - api_v3_paths.work_package(work_package.id) + api_v3_paths.work_package(work_package.display_id) ], stage: bcf_issue.stage, title: work_package.subject, @@ -175,7 +175,7 @@ RSpec.describe "BCF 2.1 topics resource", content_type: :json do modified_author: current_user.mail, modified_date: work_package.updated_at.iso8601(3), reference_links: [ - api_v3_paths.work_package(work_package.id) + api_v3_paths.work_package(work_package.display_id) ], stage: bcf_issue.stage, title: work_package.subject, @@ -220,7 +220,7 @@ RSpec.describe "BCF 2.1 topics resource", content_type: :json do modified_author: current_user.mail, modified_date: work_package.updated_at.iso8601(3), reference_links: [ - api_v3_paths.work_package(work_package.id) + api_v3_paths.work_package(work_package.display_id) ], stage: bcf_issue.stage, title: work_package.subject, @@ -234,6 +234,19 @@ RSpec.describe "BCF 2.1 topics resource", content_type: :json do end end + context "with semantic work package identifiers", + with_settings: { work_packages_identifier: "semantic" } do + it "renders the semantic identifier in the reference_links" do + work_package.reload + + expect(response).to have_http_status :ok + expect(work_package.display_id).to match(/\A#{project.identifier}-\d+\z/) + expect(response.body) + .to be_json_eql([api_v3_paths.work_package(work_package.display_id)].to_json) + .at_path("reference_links") + end + end + context "lacking permission to see project" do let(:current_user) { non_member_user } @@ -272,7 +285,7 @@ RSpec.describe "BCF 2.1 topics resource", content_type: :json do modified_author: current_user.mail, modified_date: work_package.updated_at.iso8601(3), reference_links: [ - api_v3_paths.work_package(work_package.id) + api_v3_paths.work_package(work_package.display_id) ], stage: bcf_issue.stage, title: work_package.subject, @@ -558,7 +571,7 @@ RSpec.describe "BCF 2.1 topics resource", content_type: :json do labels:, index:, reference_links: [ - api_v3_paths.work_package(work_package&.id) + api_v3_paths.work_package(work_package&.display_id) ], assigned_to: view_only_user.mail, due_date: Date.today.iso8601, @@ -597,7 +610,7 @@ RSpec.describe "BCF 2.1 topics resource", content_type: :json do labels: [], index: nil, reference_links: [ - api_v3_paths.work_package(work_package&.id) + api_v3_paths.work_package(work_package&.display_id) ], assigned_to: assigned_to || base&.assigned_to&.mail, due_date: due_date || base&.due_date, @@ -661,6 +674,68 @@ RSpec.describe "BCF 2.1 topics resource", content_type: :json do end end + context "with semantic work package identifiers", + with_settings: { work_packages_identifier: "semantic" } do + let(:params) do + { + title: "BCF topic #{existing_work_package.display_id}", + reference_links: [ + api_v3_paths.work_package(existing_work_package.display_id) + ] + } + end + + it_behaves_like "bcf api successful response" do + let(:expected_status) { 201 } + let(:expected_body) do + expected_body_for_bcf_issue( + Bim::Bcf::Issue.last.reload, + title: params[:title], + creation_author_mail: existing_work_package.author.mail, + modified_author_mail: edit_member_user.mail, + base: existing_work_package + ) + end + end + + context "with a link using the numeric id" do + let(:params) do + { + title: "BCF topic ##{existing_work_package.id}", + reference_links: [ + api_v3_paths.work_package(existing_work_package.id) + ] + } + end + + it_behaves_like "bcf api successful response" do + let(:expected_status) { 201 } + let(:expected_body) do + expected_body_for_bcf_issue( + Bim::Bcf::Issue.last.reload, + title: params[:title], + creation_author_mail: existing_work_package.author.mail, + modified_author_mail: edit_member_user.mail, + base: existing_work_package + ) + end + end + end + + context "with a link to a non-existing semantic identifier" do + let(:params) do + { + title: "A new BCF topic", + reference_links: [ + api_v3_paths.work_package("#{project.identifier}-99999") + ] + } + end + + it_behaves_like "bcf api not found response" + end + end + context "with a non-existing work package" do let(:params) do { @@ -807,7 +882,7 @@ RSpec.describe "BCF 2.1 topics resource", content_type: :json do labels: [], index:, reference_links: [ - api_v3_paths.work_package(work_package&.id) + api_v3_paths.work_package(work_package&.display_id) ], assigned_to: view_only_user.mail, due_date: Date.today.iso8601, @@ -846,7 +921,7 @@ RSpec.describe "BCF 2.1 topics resource", content_type: :json do labels: [], index: nil, reference_links: [ - api_v3_paths.work_package(work_package&.id) + api_v3_paths.work_package(work_package&.display_id) ], assigned_to: nil, due_date: nil, diff --git a/modules/bim/spec/services/bcf/issues/create_service_spec.rb b/modules/bim/spec/services/bcf/issues/create_service_spec.rb index 0bfffef3b5f..a47fc5b6b26 100644 --- a/modules/bim/spec/services/bcf/issues/create_service_spec.rb +++ b/modules/bim/spec/services/bcf/issues/create_service_spec.rb @@ -50,4 +50,63 @@ RSpec.describe Bim::Bcf::Issues::CreateService, type: :model do end end end + + describe "resolving work packages from reference_links" do + include API::V3::Utilities::PathHelper + + let(:project) { create(:project, enabled_module_names: %i[bim work_package_tracking]) } + let(:user) do + create(:user, + member_with_permissions: { project => %i[manage_bcf + view_linked_issues + view_work_packages + edit_work_packages] }) + end + let(:work_package) { create(:work_package, project:) } + let(:instance) { described_class.new(user:) } + + subject(:service_call) { instance.call(reference_links:) } + + context "with a link containing the numeric id" do + let(:reference_links) { [api_v3_paths.work_package(work_package.id)] } + + it "links the topic to the referenced work package" do + expect(service_call).to be_success + expect(service_call.result.work_package).to eq work_package + end + end + + context "with semantic work package identifiers", + with_settings: { work_packages_identifier: "semantic" } do + let(:project) { create(:project, identifier: "SEMID", enabled_module_names: %i[bim work_package_tracking]) } + + context "with a link containing the semantic identifier" do + let(:reference_links) { [api_v3_paths.work_package(work_package.display_id)] } + + it "links the topic to the referenced work package" do + expect(work_package.display_id).to start_with "SEMID-" + expect(service_call).to be_success + expect(service_call.result.work_package).to eq work_package + end + end + + context "with a link containing the numeric id" do + let(:reference_links) { [api_v3_paths.work_package(work_package.id)] } + + it "links the topic to the referenced work package" do + expect(service_call).to be_success + expect(service_call.result.work_package).to eq work_package + end + end + + context "with a link to an unknown semantic identifier" do + let(:reference_links) { [api_v3_paths.work_package("SEMID-9999")] } + + it "fails with a not found error" do + expect(service_call).to be_failure + expect(service_call.errors.symbols_for(:base)).to include :error_not_found + end + end + end + end end