Transfer topics on organization group transfer

What does this MR do and why?

Implements transfer support for the topics and project_topic_uploads tables during organization group transfer.

Topics have a unique constraint on (organization_id, name), so they cannot be simply updated in place with update_organization_id_for. Instead, this MR introduces a dedicated Organizations::Transfer::TopicsService that uses a find-or-create + re-point pattern:

  1. For each topic in the old organization referenced by projects in the transferring group hierarchy, find or create a matching topic (by name) in the target organization
  2. Re-point project_topics from the old topic to the new topic, handling the case where a project already has the target topic assigned (delete the duplicate instead of violating the unique constraint)
  3. Adjust total_projects_count and non_private_projects_count counter caches via batched delta accumulation — deltas are accumulated per-topic during the loop and flushed in a single pass at the end, reducing counter UPDATE statements from 2N to N (one per affected topic)

Additional changes

  • db/docs/topics.yml: Marked as supported since topics are now handled by the transfer service
  • db/docs/project_topic_uploads.yml: Marked as no_work_needed since it derives its organization_id via the topic's uploads_sharding_key
  • Spec housekeeping: Added feature_category: :groups_and_projects to project_topic_spec.rb and topic_spec.rb

Depends on: !243721 (merged)

References

Relates to #594592 (closed)

How to set up and validate locally

# In rails console
old_org = Organizations::Organization.find(...)
new_org = Organizations::Organization.find(...)
group = Group.find(...)
user = User.find(...)

# Verify topics exist on projects
group.all_projects.flat_map(&:topics).map(&:name)

# Execute transfer
result = Organizations::Transfer::GroupsService.new(
  group: group, new_organization: new_org, current_user: user
).execute

# Verify topics were transferred
group.reload.all_projects.flat_map { |p| p.reload.topics }.map { |t| [t.name, t.organization_id] }

MR acceptance checklist

  • Tests added for all new functionality
  • All existing tests pass
  • RuboCop passes with no offenses
Edited by tim mccarthy

Merge request reports

Loading
Loading