Commit 376dafb0 authored by Stan Hu's avatar Stan Hu

Merge branch '218609-bitbucket-server-importer-by-slug' into 'master'

Add user mapping by username to Bitbucket Server importer

Closes #218609

See merge request gitlab-org/gitlab!36885
parents d93fc08b efb28aa4
---
title: Add user mapping by username when importing projects for Bitbucket Server importer
merge_request: 36885
author:
type: added
---
name: bitbucket_server_user_mapping_by_username
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36885
rollout_issue_url:
group: group::import
type: development
default_enabled: false
...@@ -62,6 +62,25 @@ The importer will create any new namespaces (groups) if they don't exist or in ...@@ -62,6 +62,25 @@ The importer will create any new namespaces (groups) if they don't exist or in
the case the namespace is taken, the repository will be imported under the user's the case the namespace is taken, the repository will be imported under the user's
namespace that started the import process. namespace that started the import process.
#### User assignment by username
Alternatively, user assignment by username is available behind a `bitbucket_server_user_mapping_by_username` feature flag.
The importer will try to find a user in the GitLab user database using author's `username` or `slug` or `displayName`.
Falls back to author's `email` if user is not found by username.
Similarly to user assignment by email, if no such user is available, the project creator is set as the author.
To enable or disable user assignment by username:
Start a [Rails console](../../../administration/troubleshooting/debug.md#starting-a-rails-console-session).
```ruby
# Enable
Feature.enable(:bitbucket_server_user_mapping_by_username)
# Disable
Feature.disable(:bitbucket_server_user_mapping_by_username)
```
## Importing your Bitbucket repositories ## Importing your Bitbucket repositories
1. Sign in to GitLab and go to your dashboard. 1. Sign in to GitLab and go to your dashboard.
......
...@@ -38,7 +38,9 @@ module BitbucketServer ...@@ -38,7 +38,9 @@ module BitbucketServer
end end
def author_username def author_username
author['displayName'] author['username'] ||
author['slug'] ||
author['displayName']
end end
def author_email def author_email
......
...@@ -11,6 +11,12 @@ module BitbucketServer ...@@ -11,6 +11,12 @@ module BitbucketServer
raw.dig('author', 'user', 'emailAddress') raw.dig('author', 'user', 'emailAddress')
end end
def author_username
raw.dig('author', 'user', 'username') ||
raw.dig('author', 'user', 'slug') ||
raw.dig('author', 'user', 'displayName')
end
def description def description
raw['description'] raw['description']
end end
......
...@@ -61,17 +61,18 @@ module Gitlab ...@@ -61,17 +61,18 @@ module Gitlab
}.to_json) }.to_json)
end end
def gitlab_user_id(email) def find_user_id(by:, value:)
find_user_id(email) || project.creator_id return unless value
end
def find_user_id(email) return users[value] if users.key?(value)
return unless email
return users[email] if users.key?(email) user = if by == :email
User.find_by_any_email(value, confirmed: true)
else
User.find_by_username(value)
end
user = User.find_by_any_email(email, confirmed: true) users[value] = user&.id
users[email] = user&.id
user&.id user&.id
end end
...@@ -197,9 +198,8 @@ module Gitlab ...@@ -197,9 +198,8 @@ module Gitlab
log_info(stage: 'import_bitbucket_pull_requests', message: 'starting', iid: pull_request.iid) log_info(stage: 'import_bitbucket_pull_requests', message: 'starting', iid: pull_request.iid)
description = '' description = ''
description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author_email) description += author_line(pull_request)
description += pull_request.description if pull_request.description description += pull_request.description if pull_request.description
author_id = gitlab_user_id(pull_request.author_email)
attributes = { attributes = {
iid: pull_request.iid, iid: pull_request.iid,
...@@ -212,7 +212,7 @@ module Gitlab ...@@ -212,7 +212,7 @@ module Gitlab
target_branch: Gitlab::Git.ref_name(pull_request.target_branch_name), target_branch: Gitlab::Git.ref_name(pull_request.target_branch_name),
target_branch_sha: pull_request.target_branch_sha, target_branch_sha: pull_request.target_branch_sha,
state_id: MergeRequest.available_states[pull_request.state], state_id: MergeRequest.available_states[pull_request.state],
author_id: author_id, author_id: author_id(pull_request),
created_at: pull_request.created_at, created_at: pull_request.created_at,
updated_at: pull_request.updated_at updated_at: pull_request.updated_at
} }
...@@ -254,7 +254,7 @@ module Gitlab ...@@ -254,7 +254,7 @@ module Gitlab
committer = merge_event.committer_email committer = merge_event.committer_email
user_id = gitlab_user_id(committer) user_id = find_user_id(by: :email, value: committer) || project.creator_id
timestamp = merge_event.merge_timestamp timestamp = merge_event.merge_timestamp
merge_request.update({ merge_commit_sha: merge_event.merge_commit }) merge_request.update({ merge_commit_sha: merge_event.merge_commit })
metric = MergeRequest::Metrics.find_or_initialize_by(merge_request: merge_request) metric = MergeRequest::Metrics.find_or_initialize_by(merge_request: merge_request)
...@@ -353,7 +353,7 @@ module Gitlab ...@@ -353,7 +353,7 @@ module Gitlab
end end
def pull_request_comment_attributes(comment) def pull_request_comment_attributes(comment)
author = find_user_id(comment.author_email) author = uid(comment)
note = '' note = ''
unless author unless author
...@@ -397,6 +397,23 @@ module Gitlab ...@@ -397,6 +397,23 @@ module Gitlab
def metrics def metrics
@metrics ||= Gitlab::Import::Metrics.new(:bitbucket_server_importer, @project) @metrics ||= Gitlab::Import::Metrics.new(:bitbucket_server_importer, @project)
end end
def author_line(rep_object)
return '' if uid(rep_object)
@formatter.author_line(rep_object.author)
end
def author_id(rep_object)
uid(rep_object) || project.creator_id
end
def uid(rep_object)
find_user_id(by: :email, value: rep_object.author_email) unless Feature.enabled?(:bitbucket_server_user_mapping_by_username)
find_user_id(by: :username, value: rep_object.author_username) ||
find_user_id(by: :email, value: rep_object.author_email)
end
end end
end end
end end
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [ "comments": [
...@@ -38,7 +39,8 @@ ...@@ -38,7 +39,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [ "comments": [
...@@ -56,7 +58,8 @@ ...@@ -56,7 +58,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [], "comments": [],
...@@ -85,7 +88,8 @@ ...@@ -85,7 +88,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"createdDate": 1530164016725, "createdDate": 1530164016725,
...@@ -115,7 +119,8 @@ ...@@ -115,7 +119,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"createdDate": 1530164026000, "createdDate": 1530164026000,
...@@ -147,7 +152,8 @@ ...@@ -147,7 +152,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [], "comments": [],
...@@ -194,7 +200,8 @@ ...@@ -194,7 +200,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [], "comments": [],
...@@ -363,7 +370,8 @@ ...@@ -363,7 +370,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
} }
}, },
...@@ -383,7 +391,8 @@ ...@@ -383,7 +391,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [], "comments": [],
...@@ -543,7 +552,8 @@ ...@@ -543,7 +552,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
} }
}, },
...@@ -563,7 +573,8 @@ ...@@ -563,7 +573,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [ "comments": [
...@@ -581,7 +592,8 @@ ...@@ -581,7 +592,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [ "comments": [
...@@ -599,7 +611,8 @@ ...@@ -599,7 +611,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [], "comments": [],
...@@ -789,7 +802,8 @@ ...@@ -789,7 +802,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
} }
}, },
...@@ -809,7 +823,8 @@ ...@@ -809,7 +823,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [], "comments": [],
...@@ -843,7 +858,8 @@ ...@@ -843,7 +858,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
} }
}, },
...@@ -863,7 +879,8 @@ ...@@ -863,7 +879,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"authorTimestamp": 1529727872000, "authorTimestamp": 1529727872000,
...@@ -880,7 +897,8 @@ ...@@ -880,7 +897,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"committerTimestamp": 1529727872000, "committerTimestamp": 1529727872000,
...@@ -951,7 +969,8 @@ ...@@ -951,7 +969,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
} }
}, },
...@@ -971,7 +990,8 @@ ...@@ -971,7 +990,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [ "comments": [
...@@ -989,7 +1009,8 @@ ...@@ -989,7 +1009,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [], "comments": [],
...@@ -1038,7 +1059,8 @@ ...@@ -1038,7 +1059,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
} }
}, },
...@@ -1058,7 +1080,8 @@ ...@@ -1058,7 +1080,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
}, },
"comments": [], "comments": [],
...@@ -1092,7 +1115,8 @@ ...@@ -1092,7 +1115,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
} }
}, },
...@@ -1113,7 +1137,8 @@ ...@@ -1113,7 +1137,8 @@
] ]
}, },
"name": "root", "name": "root",
"slug": "root", "slug": "slug",
"username": "username",
"type": "NORMAL" "type": "NORMAL"
} }
} }
......
...@@ -5,8 +5,9 @@ ...@@ -5,8 +5,9 @@
"status":"UNAPPROVED", "status":"UNAPPROVED",
"user":{ "user":{
"active":true, "active":true,
"displayName":"root", "displayName":"displayName",
"emailAddress":"joe.montana@49ers.com", "emailAddress":"joe.montana@49ers.com",
"username": "username",
"id":1, "id":1,
"links":{ "links":{
"self":[ "self":[
...@@ -16,7 +17,7 @@ ...@@ -16,7 +17,7 @@
] ]
}, },
"name":"root", "name":"root",
"slug":"root", "slug":"slug",
"type":"NORMAL" "type":"NORMAL"
} }
}, },
......
...@@ -13,7 +13,30 @@ RSpec.describe BitbucketServer::Representation::Comment do ...@@ -13,7 +13,30 @@ RSpec.describe BitbucketServer::Representation::Comment do
end end
describe '#author_username' do describe '#author_username' do
it { expect(subject.author_username).to eq('root' ) } it 'returns username' do
expect(subject.author_username).to eq('username')
end
context 'when username is absent' do
before do
comment['comment']['author'].delete('username')
end
it 'returns slug' do
expect(subject.author_username).to eq('slug')
end
end
context 'when slug and username are absent' do
before do
comment['comment']['author'].delete('username')
comment['comment']['author'].delete('slug')
end
it 'returns displayName' do
expect(subject.author_username).to eq('root')
end
end
end end
describe '#author_email' do describe '#author_email' do
......
...@@ -15,6 +15,33 @@ RSpec.describe BitbucketServer::Representation::PullRequest do ...@@ -15,6 +15,33 @@ RSpec.describe BitbucketServer::Representation::PullRequest do
it { expect(subject.author_email).to eq('joe.montana@49ers.com') } it { expect(subject.author_email).to eq('joe.montana@49ers.com') }
end end
describe '#author_username' do
it 'returns username' do
expect(subject.author_username).to eq('username')
end
context 'when username is absent' do
before do
sample_data['author']['user'].delete('username')
end
it 'returns slug' do
expect(subject.author_username).to eq('slug')
end
end
context 'when slug and username are absent' do
before do
sample_data['author']['user'].delete('username')
sample_data['author']['user'].delete('slug')
end
it 'returns displayName' do
expect(subject.author_username).to eq('displayName')
end
end
end
describe '#description' do describe '#description' do
it { expect(subject.description).to eq('Test') } it { expect(subject.description).to eq('Test') }
end 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