diff --git a/app/models/import/jira_user.rb b/app/models/import/jira_user.rb index 258b70fa2b6..fc657c4d4eb 100644 --- a/app/models/import/jira_user.rb +++ b/app/models/import/jira_user.rb @@ -51,8 +51,8 @@ module Import def try_to_find_existing_op_users op_attributes = to_op_attributes - User.where(login: op_attributes[:login]).or( - User.where(mail: op_attributes[:mail]) + User.by_login(op_attributes[:login]).or( + User.where("LOWER(mail) = ?", op_attributes[:mail]&.downcase) ) end diff --git a/app/workers/import/jira_import_projects_job.rb b/app/workers/import/jira_import_projects_job.rb index ee7b0a71ee3..2d1f6b5a4aa 100644 --- a/app/workers/import/jira_import_projects_job.rb +++ b/app/workers/import/jira_import_projects_job.rb @@ -384,10 +384,16 @@ module Import jira_user = Import::JiraUser.find_by(jira_user_key:, jira_import: @jira_import) if jira_user - JiraOpenProjectReference.find_by!( - jira_entity_class: "Import::JiraUser", + reference = JiraOpenProjectReference.find_by( + jira_entity_class: jira_user.class.to_s, jira_entity_id: jira_user.id - ).op_leg + ) + if reference + reference.op_leg + else + raise "Import::JiraOpenProjectReference with jira_entity_class #{jira_user.class} " \ + "and jira_entity_id #{jira_user.id} not found!" + end else raise "Import::JiraUser with jira_user_key #{jira_user_key} not found!" end diff --git a/config/initializers/retry_failed_jobs_with_name_error.rb b/config/initializers/retry_failed_jobs_with_name_error.rb index add24ea1a17..9fbed7789bb 100644 --- a/config/initializers/retry_failed_jobs_with_name_error.rb +++ b/config/initializers/retry_failed_jobs_with_name_error.rb @@ -42,6 +42,7 @@ Rails.application.configure do GoodJob::Job .discarded + .where.not(error_event: GoodJob::ErrorEvents::RETRIED) # reject discarded jobs that were already retried .where("error LIKE ?", "NameError: uninitialized constant %") .filter do |job| # Only retry jobs with NameError related to the job class name. diff --git a/docs/release-notes/17-5-0/README.md b/docs/release-notes/17-5-0/README.md index 58e864e9df9..045a4986c89 100644 --- a/docs/release-notes/17-5-0/README.md +++ b/docs/release-notes/17-5-0/README.md @@ -49,9 +49,9 @@ Existing integrations such as GitHub and GitLab already support the new identifi [See our system admin guide for detailed information on how to manage work package identifiers](../../system-admin-guide/manage-work-packages/work-package-identifiers/). -#### Releasing unused numerical identifiers +#### Releasing unused project identifiers -When switching from the default numerical sequence to project-based work package identifiers, previously reserved numerical identifiers can be released again if they are no longer needed. This helps administrators avoid unnecessary gaps and keep numerical identifiers available if they later revert to the default sequence. +OpenProject allows administrators to release reserved project identifiers that are no longer needed. Please note that this option is currently only available when numerical work package identifiers are enabled. > [!NOTE] > Releasing an identifier cannot be undone. External links and integrations using it will stop resolving, and the name becomes available for any new project to claim. diff --git a/spec/models/import/jira_user_spec.rb b/spec/models/import/jira_user_spec.rb index e0f2b00acec..8130c12a0a9 100644 --- a/spec/models/import/jira_user_spec.rb +++ b/spec/models/import/jira_user_spec.rb @@ -110,6 +110,69 @@ RSpec.describe Import::JiraUser do end end + describe "#try_to_find_existing_op_users" do + subject(:result) { jira_user.try_to_find_existing_op_users } + + let(:payload) { { "displayName" => "Test User", "name" => "testuser", "emailAddress" => "test@example.com" } } + + context "when no matching user exists" do + it "returns an empty relation" do + expect(result).to be_empty + end + end + + context "when a user with matching login exists (case-insensitive)" do + let!(:existing_user) { create(:user, login: "TestUser", mail: "other@example.com") } + + it "finds the user" do + expect(result).to contain_exactly(existing_user) + end + end + + context "when a user with matching email exists (case-insensitive)" do + let!(:existing_user) { create(:user, login: "otherlogin", mail: "TEST@EXAMPLE.COM") } + + it "finds the user" do + expect(result).to contain_exactly(existing_user) + end + end + + context "when a user matches both login and email" do + let!(:existing_user) { create(:user, login: "TESTUSER", mail: "TEST@example.com") } + + it "finds the user once" do + expect(result).to contain_exactly(existing_user) + end + end + + context "when different users match login and email respectively" do + let!(:user_by_login) { create(:user, login: "TESTUSER", mail: "different@example.com") } + let!(:user_by_email) { create(:user, login: "differentlogin", mail: "TEST@EXAMPLE.COM") } + + it "finds both users" do + expect(result).to contain_exactly(user_by_login, user_by_email) + end + end + + context "when email is nil in payload" do + let(:payload) { { "displayName" => "Test User", "name" => "testuser", "emailAddress" => nil } } + let!(:existing_user) { create(:user, login: "TESTUSER", mail: "any@example.com") } + + it "still finds users by login" do + expect(result).to contain_exactly(existing_user) + end + end + + context "when email separator is used" do + let(:payload) { { "displayName" => "Test User", "name" => "othername", "emailAddress" => "any@example.com" } } + let!(:existing_user) { create(:user, login: "testname", mail: "any+test@example.com") } + + it "considers the email to be different and does not find it this user account" do + expect(result).to be_empty + end + end + end + describe "#sanitize_name (private)" do subject(:jira_user) { described_class.new(payload: {}) }