User mapping - Reassignment process
We will work on the job/class that will perform the reassignment on this issue.
We should create a background job that calls the class to perform the reassignment. The background can receive an Import::SourceUser
ID as an argument, as the Import::SourceUser
record will have the placeholder user currently being used and the user to whom the contributions should be reassigned.
The process involves scanning all imported objects and replacing any references associated with a placeholder user with the assigned user. Additionally, all imported contributions should be reassigned under the placeholder user's top-level namespace, including contributions under sub-groups and projects.
A similar service Users::MigrateRecordsToGhostUserService but the service only consider a few tables. In our case, we should consider all tables where resources are imported.
Ultimately, we should change the Import::SourceUser
status to complete
and delete the placeholder user.
Note: On the POC, the reassignment class relied on the import_details
record for its solution for simplicity. However, we shouldn't rely on these records, as they will only be created for resources assigned to the Importer User.
!156241 (merged) introduces the model and table that holds the tracking data of importer user references to map. A very basic example of its use in the reassignment process is below:
# A naive demo of the concept, not intended to be real code
# in Import::SourceUserPlaceholderReference model:
# def find_by
# return composite_key if composite_key.present?
#
# { model.constantize.primary_key => numeric_key }
# end
def example_map_contributions_for_user(source_user)
# repeat until there are no more contributions to map
record = Import::SourceUserContribution.find_by(source_user: source_user)
return unless record
model = record.model.safe_constantize
return unless model # This shouldn't happen after https://gitlab.com/gitlab-org/gitlab/-/issues/467522
Import::SourceUserContribution.where(
source_user: source_user,
model: model.name,
column: record.user_reference_column
).each_batch(of: 500) do |batch|
new_attributes = { record.column => source_user.reassigned_to_user_id }
new_attributes[:importing] = true if model < Importable # If we update using callbacks we would need this
# Programmatically build an `or` statement
# (may be better done in Arel, but an example of outcome):
relations = model.where(x.find_by).or(model.where(y.find_by))
# Then update.
#
# I wonder if it would be best to update with callbacks switched on, or off?
# It could be great to use `BulkInsertSafe` if it's available to the model?
end
example_map_contributions_for_user(source_user)
end