Commit 94f09a46 authored by Alexandru Croitor's avatar Alexandru Croitor

Update SQL to match users by email, name or username

Try to do exact match jira users by either email, username or name
to link the matched user to gitlab user.
parent 58d7d691
......@@ -350,6 +350,9 @@ class User < ApplicationRecord
scope :deactivated, -> { with_state(:deactivated).non_internal }
scope :without_projects, -> { joins('LEFT JOIN project_authorizations ON users.id = project_authorizations.user_id').where(project_authorizations: { user_id: nil }) }
scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) }
scope :by_name, -> (names) { iwhere(name: Array(names)) }
scope :by_user_email, -> (emails) { iwhere(email: Array(emails)) }
scope :by_emails, -> (emails) { joins(:emails).where(emails: { email: Array(emails).map(&:downcase) }) }
scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) }
scope :with_emails, -> { preload(:emails) }
scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) }
......@@ -509,54 +512,20 @@ class User < ApplicationRecord
by_any_email(email, confirmed: confirmed).take
end
# Returns a relation containing all users with emails for given emails
# where non-confirmed emails are included and private commit emails are skiped
# or names where username and name is searched for exact match
#
#
# @param emails [String, Array<String>] email addresses to check
# @param names [String, Array<String>] names to check in username and name
def by_emails_or_names(jira_pairs)
sql = <<~SQL
WITH jira_users(name, email) AS (VALUES #{jira_pairs})
SELECT DISTINCT users.id AS user_id,
(CASE
WHEN ((jira_users.email = users.email) OR (jira_users.email = emails.email))
THEN 3
WHEN (jira_users.name = lower(users.username))
THEN 2
WHEN (jira_users.name = lower(users.name))
THEN 1
END)
AS match_score, jira_users.name as jira_name, jira_users.email as jira_email
FROM users
LEFT JOIN emails ON (users.id = emails.user_id)
JOIN jira_users ON (jira_users.name = lower(users.username))
OR (jira_users.name = lower(users.name))
OR (jira_users.email = users.email)
OR (jira_users.email = emails.email)
ORDER BY match_score DESC;
SQL
ActiveRecord::Base.connection.execute(sql)
end
# Returns a relation containing all the users for the given email addresses
#
# @param emails [String, Array<String>] email addresses to check
# @param confirmed [Boolean] Only return users where the email is confirmed
def by_any_email(emails, confirmed: false)
emails = Array(emails).map(&:downcase)
from_users = where(email: emails)
from_users = by_user_email(emails)
from_users = from_users.confirmed if confirmed
from_emails = joins(:emails).where(emails: { email: emails })
from_emails = by_emails(emails)
from_emails = from_emails.confirmed.merge(Email.confirmed) if confirmed
items = [from_users, from_emails]
user_ids = Gitlab::PrivateCommitEmail.user_ids_for_emails(emails)
user_ids = Gitlab::PrivateCommitEmail.user_ids_for_emails(Array(emails).map(&:downcase))
items << where(id: user_ids) if user_ids.present?
from_union(items)
......
......@@ -53,33 +53,31 @@ module JiraImport
def matched_users
strong_memoize(:matched_users) do
pairs_to_match = jira_users.map do |user|
"('#{jira_user_name(user)&.downcase}', '#{user['emailAddress']&.downcase}')"
end.join(',')
jira_emails = jira_users.map { |u| u['emailAddress']&.downcase }.compact
jira_names = jira_users.map { |u| jira_user_name(u)&.downcase }.compact
User.by_emails_or_names(pairs_to_match)
relations = []
relations << User.by_username(jira_names).select("users.id, users.name, users.username, users.email as user_email")
relations << User.by_name(jira_names).select("users.id, users.name, users.username, users.email as user_email")
relations << User.by_user_email(jira_emails).select("users.id, users.name, users.username, users.email as user_email")
relations << User.by_emails(jira_emails).select("users.id, users.name, users.username, emails.email as user_email")
User.from_union(relations).id_in(project_member_ids).select("users.id as user_id, users.name as name, users.username as username, user_email")
end
end
def find_gitlab_id(jira_user)
user = matched_users.find do |matched_user|
matched_user['jira_email'] == jira_user['emailAddress']&.downcase ||
matched_user['jira_name'].downcase == jira_user_name(jira_user)&.downcase
matched_user.user_email&.downcase == jira_user['emailAddress']&.downcase ||
matched_user.name&.downcase == jira_user_name(jira_user)&.downcase ||
matched_user.username&.downcase == jira_user_name(jira_user)&.downcase
end
return unless user
user_id = user['user_id']
return unless project_member_ids.include?(user_id)
user_id
user&.user_id
end
def project_member_ids
# rubocop: disable CodeReuse/ActiveRecord
@project_member_ids ||= MembersFinder.new(project, current_user).execute.pluck(:user_id)
# rubocop: enable CodeReuse/ActiveRecord
@project_member_ids ||= MembersFinder.new(project, current_user).execute.select(:user_id)
end
end
end
---
title: Match Jira users by email, username or name on jira issues import
merge_request: 33883
author:
type: changed
......@@ -7,11 +7,11 @@ RSpec.describe JiraImport::CloudUsersMapperService do
let(:url) { "/rest/api/2/users?maxResults=50&startAt=#{start_at}" }
let_it_be(:user_1) { create(:user, username: 'randomuser', name: 'USER-name1', email: 'uji@example.com') }
let_it_be(:user_2) { create(:user, username: 'username2') }
let_it_be(:user_5) { create(:user, username: 'username5') }
let_it_be(:user_4) { create(:user, email: 'user4@example.com') }
let_it_be(:user_6) { create(:user, email: 'user6@example.com') }
let_it_be(:user_7) { create(:user, username: 'username7') }
let_it_be(:user_2) { create(:user, username: 'username-2') }
let_it_be(:user_5) { create(:user, username: 'username-5') }
let_it_be(:user_4) { create(:user, email: 'user-4@example.com') }
let_it_be(:user_6) { create(:user, email: 'user-6@example.com') }
let_it_be(:user_7) { create(:user, username: 'username-7') }
let_it_be(:user_8) do
create(:user).tap { |user| create(:email, user: user, email: 'user8_email@example.com') }
end
......@@ -23,14 +23,14 @@ RSpec.describe JiraImport::CloudUsersMapperService do
let(:jira_users) do
[
{ 'accountId' => 'abcd', 'displayName' => 'User-Name1' }, # matched by name
{ 'accountId' => 'efg', 'displayName' => 'username2' }, # matcher by username
{ 'accountId' => 'efg', 'displayName' => 'username-2' }, # matcher by username
{ 'accountId' => 'hij' }, # no match
{ 'accountId' => '123', 'displayName' => 'user4', 'emailAddress' => 'user4@example.com' }, # matched by email
{ 'accountId' => '456', 'displayName' => 'username5foo', 'emailAddress' => 'user5@example.com' }, # no match
{ 'accountId' => '789', 'displayName' => 'user6', 'emailAddress' => 'user6@example.com' }, # matched by email, no project member
{ 'accountId' => 'xyz', 'displayName' => 'username7', 'emailAddress' => 'user7@example.com' }, # matched by username, no project member
{ 'accountId' => 'vhk', 'displayName' => 'user8', 'emailAddress' => 'user8_email@example.com' }, # matched by secondary email
{ 'accountId' => 'uji', 'displayName' => 'user9', 'emailAddress' => 'uji@example.com' } # matched by email, same as user_1
{ 'accountId' => '123', 'displayName' => 'user-4', 'emailAddress' => 'user-4@example.com' }, # matched by email
{ 'accountId' => '456', 'displayName' => 'username5foo', 'emailAddress' => 'user-5@example.com' }, # no match
{ 'accountId' => '789', 'displayName' => 'user-6', 'emailAddress' => 'user-6@example.com' }, # matched by email, no project member
{ 'accountId' => 'xyz', 'displayName' => 'username-7', 'emailAddress' => 'user-7@example.com' }, # matched by username, no project member
{ 'accountId' => 'vhk', 'displayName' => 'user-8', 'emailAddress' => 'user8_email@example.com' }, # matched by secondary email
{ 'accountId' => 'uji', 'displayName' => 'user-9', 'emailAddress' => 'uji@example.com' } # matched by email, same as user_1
]
end
......
......@@ -7,11 +7,11 @@ RSpec.describe JiraImport::ServerUsersMapperService do
let(:url) { "/rest/api/2/user/search?username=''&maxResults=50&startAt=#{start_at}" }
let_it_be(:user_1) { create(:user, username: 'randomuser', name: 'USER-name1', email: 'uji@example.com') }
let_it_be(:user_2) { create(:user, username: 'username2') }
let_it_be(:user_5) { create(:user, username: 'username5') }
let_it_be(:user_4) { create(:user, email: 'user4@example.com') }
let_it_be(:user_6) { create(:user, email: 'user6@example.com') }
let_it_be(:user_7) { create(:user, username: 'username7') }
let_it_be(:user_2) { create(:user, username: 'username-2') }
let_it_be(:user_5) { create(:user, username: 'username-5') }
let_it_be(:user_4) { create(:user, email: 'user-4@example.com') }
let_it_be(:user_6) { create(:user, email: 'user-6@example.com') }
let_it_be(:user_7) { create(:user, username: 'username-7') }
let_it_be(:user_8) do
create(:user).tap { |user| create(:email, user: user, email: 'user8_email@example.com') }
end
......@@ -23,14 +23,14 @@ RSpec.describe JiraImport::ServerUsersMapperService do
let(:jira_users) do
[
{ 'key' => 'abcd', 'name' => 'User-Name1' }, # matched by name
{ 'key' => 'efg', 'name' => 'username2' }, # matcher by username
{ 'key' => 'efg', 'name' => 'username-2' }, # matcher by username
{ 'key' => 'hij' }, # no match
{ 'key' => '123', 'name' => 'user4', 'emailAddress' => 'user4@example.com' }, # matched by email
{ 'key' => '456', 'name' => 'username5foo', 'emailAddress' => 'user5@example.com' }, # no match
{ 'key' => '789', 'name' => 'user6', 'emailAddress' => 'user6@example.com' }, # matched by email, no project member
{ 'key' => 'xyz', 'name' => 'username7', 'emailAddress' => 'user7@example.com' }, # matched by username, no project member
{ 'key' => 'vhk', 'name' => 'user8', 'emailAddress' => 'user8_email@example.com' }, # matched by secondary email
{ 'key' => 'uji', 'name' => 'user9', 'emailAddress' => 'uji@example.com' } # matched by email, same as user_1
{ 'key' => '123', 'name' => 'user-4', 'emailAddress' => 'user-4@example.com' }, # matched by email
{ 'key' => '456', 'name' => 'username5foo', 'emailAddress' => 'user-5@example.com' }, # no match
{ 'key' => '789', 'name' => 'user-6', 'emailAddress' => 'user-6@example.com' }, # matched by email, no project member
{ 'key' => 'xyz', 'name' => 'username-7', 'emailAddress' => 'user-7@example.com' }, # matched by username, no project member
{ 'key' => 'vhk', 'name' => 'user-8', 'emailAddress' => 'user8_email@example.com' }, # matched by secondary email
{ 'key' => 'uji', 'name' => 'user-9', 'emailAddress' => 'uji@example.com' } # matched by email, same as user_1
]
end
......
......@@ -24,14 +24,14 @@ RSpec.shared_examples 'mapping jira users' do
let(:mapped_users) do
[
{ jira_account_id: 'abcd', jira_display_name: 'User-Name1', jira_email: nil, gitlab_id: user_1.id },
{ jira_account_id: 'efg', jira_display_name: 'username2', jira_email: nil, gitlab_id: user_2.id },
{ jira_account_id: 'efg', jira_display_name: 'username-2', jira_email: nil, gitlab_id: user_2.id },
{ jira_account_id: 'hij', jira_display_name: nil, jira_email: nil, gitlab_id: nil },
{ jira_account_id: '123', jira_display_name: 'user4', jira_email: 'user4@example.com', gitlab_id: user_4.id },
{ jira_account_id: '456', jira_display_name: 'username5foo', jira_email: 'user5@example.com', gitlab_id: nil },
{ jira_account_id: '789', jira_display_name: 'user6', jira_email: 'user6@example.com', gitlab_id: nil },
{ jira_account_id: 'xyz', jira_display_name: 'username7', jira_email: 'user7@example.com', gitlab_id: nil },
{ jira_account_id: 'vhk', jira_display_name: 'user8', jira_email: 'user8_email@example.com', gitlab_id: user_8.id },
{ jira_account_id: 'uji', jira_display_name: 'user9', jira_email: 'uji@example.com', gitlab_id: user_1.id }
{ jira_account_id: '123', jira_display_name: 'user-4', jira_email: 'user-4@example.com', gitlab_id: user_4.id },
{ jira_account_id: '456', jira_display_name: 'username5foo', jira_email: 'user-5@example.com', gitlab_id: nil },
{ jira_account_id: '789', jira_display_name: 'user-6', jira_email: 'user-6@example.com', gitlab_id: nil },
{ jira_account_id: 'xyz', jira_display_name: 'username-7', jira_email: 'user-7@example.com', gitlab_id: nil },
{ jira_account_id: 'vhk', jira_display_name: 'user-8', jira_email: 'user8_email@example.com', gitlab_id: user_8.id },
{ jira_account_id: 'uji', jira_display_name: 'user-9', jira_email: 'uji@example.com', gitlab_id: user_1.id }
]
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment