From 5bbc4e7563ef52b7c452c8c768840482e9692d94 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Fri, 10 Apr 2026 17:18:46 +0300 Subject: [PATCH] Rename semanticId to displayId, make always present Replace the conditional `semanticId` API field with `displayId` which is always present in work package responses. In semantic mode it returns the project-based identifier (e.g. "PROJ-42"), in classic mode it returns the numeric ID as a string. This gives API consumers (frontend, mobile) a single field to read without conditional logic. - Add `WorkPackage#display_id` method that encapsulates the mode check - Update both representers (JSON and SQL) to render `displayId` unconditionally - Update OpenAPI schema documentation --- .../work_package/semantic_identifier.rb | 7 ++++++ .../components/schemas/work_package_model.yml | 7 +++--- .../work_packages/work_package_representer.rb | 7 +++--- .../work_package_sql_representer.rb | 7 +++--- .../work_package_representer_spec.rb | 9 +++---- ..._package_sql_representer_rendering_spec.rb | 25 +++++++++++-------- .../work_package/semantic_identifier_spec.rb | 17 +++++++++++++ 7 files changed, 53 insertions(+), 26 deletions(-) diff --git a/app/models/work_package/semantic_identifier.rb b/app/models/work_package/semantic_identifier.rb index d3ad6fe93b1..0d8861ac95d 100644 --- a/app/models/work_package/semantic_identifier.rb +++ b/app/models/work_package/semantic_identifier.rb @@ -76,6 +76,13 @@ module WorkPackage::SemanticIdentifier end end + # Returns the user-facing identifier for this work package. + # In semantic mode: the project-based identifier (e.g. "PROJ-42") + # In classic mode: the numeric database ID + def display_id + Setting::WorkPackageIdentifier.semantic_mode_active? ? identifier : id + end + # Allocates the next semantic identifier in the current project and assigns it to the WP. # Also writes alias rows for every identifier the project has ever used (including "ghost" aliases). # diff --git a/docs/api/apiv3/components/schemas/work_package_model.yml b/docs/api/apiv3/components/schemas/work_package_model.yml index 5e812cca2c3..ca1f134e764 100644 --- a/docs/api/apiv3/components/schemas/work_package_model.yml +++ b/docs/api/apiv3/components/schemas/work_package_model.yml @@ -12,11 +12,12 @@ allOf: description: Work package id readOnly: true minimum: 1 - semanticId: + displayId: type: string description: |- - The project-based semantic identifier for the work package (e.g. PROJ-42). - Only present when semantic mode is enabled. + The user-facing identifier for the work package. + In semantic mode: the project-based identifier (e.g. "PROJ-42"). + In classic mode: the numeric ID as a string (e.g. "123"). readOnly: true lockVersion: type: integer diff --git a/lib/api/v3/work_packages/work_package_representer.rb b/lib/api/v3/work_packages/work_package_representer.rb index e59da4b1962..907ef4769c3 100644 --- a/lib/api/v3/work_packages/work_package_representer.rb +++ b/lib/api/v3/work_packages/work_package_representer.rb @@ -345,11 +345,10 @@ module API property :id, render_nil: true - property :semantic_id, - as: :semanticId, + property :display_id, + as: :displayId, render_nil: true, - getter: ->(*) { identifier }, - if: ->(*) { Setting::WorkPackageIdentifier.semantic_mode_active? } + getter: ->(*) { display_id&.to_s } property :lock_version, render_nil: true, diff --git a/lib/api/v3/work_packages/work_package_sql_representer.rb b/lib/api/v3/work_packages/work_package_sql_representer.rb index d8e1c118a77..84a7b89ec9a 100644 --- a/lib/api/v3/work_packages/work_package_sql_representer.rb +++ b/lib/api/v3/work_packages/work_package_sql_representer.rb @@ -76,9 +76,10 @@ module API property :id - property :semanticId, - representation: ->(*) { "identifier" }, - render_if: ->(*) { Setting::WorkPackageIdentifier.semantic_mode_active? ? "TRUE" : "FALSE" } + property :displayId, + representation: ->(*) { + Setting::WorkPackageIdentifier.semantic_mode_active? ? "identifier" : "id::text" + } property :subject diff --git a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb index 1dc9dcde20f..459ce56e728 100644 --- a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb @@ -160,16 +160,15 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter do let(:value) { work_package.id } end - describe "semanticId" do + describe "displayId" do context "when semantic work package ids are active", with_flag: { semantic_work_package_ids: true }, with_settings: { work_packages_identifier: "semantic" } do - it { is_expected.to be_json_eql(work_package.identifier.to_json).at_path("semanticId") } + it { is_expected.to be_json_eql(work_package.identifier.to_json).at_path("displayId") } end - context "when semantic_work_package_ids feature flag is inactive", - with_flag: { semantic_work_package_ids: false } do - it { is_expected.not_to have_json_path("semanticId") } + context "when semantic work package ids are not active" do + it { is_expected.to be_json_eql(work_package.id.to_s.to_json).at_path("displayId") } end end diff --git a/spec/lib/api/v3/work_packages/work_package_sql_representer_rendering_spec.rb b/spec/lib/api/v3/work_packages/work_package_sql_representer_rendering_spec.rb index 3ffa4445810..4ab569ee70e 100644 --- a/spec/lib/api/v3/work_packages/work_package_sql_representer_rendering_spec.rb +++ b/spec/lib/api/v3/work_packages/work_package_sql_representer_rendering_spec.rb @@ -72,6 +72,7 @@ RSpec.describe API::V3::WorkPackages::WorkPackageSqlRepresenter, "rendering" do { _type: "WorkPackage", id: rendered_work_package.id, + displayId: rendered_work_package.id.to_s, subject: rendered_work_package.subject, dueDate: rendered_work_package.due_date, startDate: rendered_work_package.start_date, @@ -118,6 +119,7 @@ RSpec.describe API::V3::WorkPackages::WorkPackageSqlRepresenter, "rendering" do { _type: "WorkPackage", id: rendered_work_package.id, + displayId: rendered_work_package.id.to_s, subject: rendered_work_package.subject, date: rendered_work_package.start_date, _links: { @@ -156,20 +158,21 @@ RSpec.describe API::V3::WorkPackages::WorkPackageSqlRepresenter, "rendering" do end end - context "when semantic work package ids are active", - with_flag: { semantic_work_package_ids: true }, - with_settings: { work_packages_identifier: "semantic" } do - let(:project) { create(:project, identifier: "PROJ", types: [type]) } + describe "displayId" do + context "when semantic work package ids are active", + with_flag: { semantic_work_package_ids: true }, + with_settings: { work_packages_identifier: "semantic" } do + let(:project) { create(:project, identifier: "PROJ", types: [type]) } - it "includes semanticId" do - expect(json).to be_json_eql("PROJ-1".to_json).at_path("semanticId") + it "returns the semantic identifier" do + expect(json).to be_json_eql("PROJ-1".to_json).at_path("displayId") + end end - end - context "when semantic_work_package_ids feature flag is inactive", - with_flag: { semantic_work_package_ids: false } do - it "does not include semanticId" do - expect(json).not_to have_json_path("semanticId") + context "when semantic work package ids are not active" do + it "returns the numeric id as a string" do + expect(json).to be_json_eql(rendered_work_package.id.to_s.to_json).at_path("displayId") + end end end end diff --git a/spec/models/work_package/semantic_identifier_spec.rb b/spec/models/work_package/semantic_identifier_spec.rb index 4061a7436a7..6d41e2215a6 100644 --- a/spec/models/work_package/semantic_identifier_spec.rb +++ b/spec/models/work_package/semantic_identifier_spec.rb @@ -136,6 +136,23 @@ RSpec.describe WorkPackage::SemanticIdentifier do end end + describe "#display_id" do + context "when semantic mode is active", + with_flag: { semantic_work_package_ids: true }, + with_settings: { work_packages_identifier: "semantic" } do + it "returns the semantic identifier" do + expect(work_package.display_id).to eq("MYPROJ-1") + end + end + + context "when semantic mode is not active", + with_flag: { semantic_work_package_ids: false } do + it "returns the numeric id" do + expect(work_package.display_id).to eq(work_package.id) + end + end + end + describe "#allocate_and_register_semantic_id" do let(:project) { create(:project, identifier: "PROJ", wp_sequence_counter: 0) } let(:target_project) { create(:project, identifier: "OTHER", wp_sequence_counter: 0) }