summaryrefslogtreecommitdiff
path: root/app/services/topics/merge_service.rb
blob: 58f3d5305b48efbcbc911fff7cd1af356488b453 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# frozen_string_literal: true

module Topics
  class MergeService
    attr_accessor :source_topic, :target_topic

    def initialize(source_topic, target_topic)
      @source_topic = source_topic
      @target_topic = target_topic
    end

    def execute
      validate_parameters!

      ::Projects::ProjectTopic.transaction do
        move_project_topics
        refresh_target_topic_counters
        delete_source_topic
      end

      ServiceResponse.success
    rescue ArgumentError => e
      ServiceResponse.error(message: e.message)
    rescue StandardError => e
      Gitlab::ErrorTracking.track_exception(e, source_topic_id: source_topic.id, target_topic_id: target_topic.id)
      ServiceResponse.error(message: _('Topics could not be merged!'))
    end

    private

    def validate_parameters!
      raise ArgumentError, _('The source topic is not a topic.') unless source_topic.is_a?(Projects::Topic)
      raise ArgumentError, _('The target topic is not a topic.') unless target_topic.is_a?(Projects::Topic)
      raise ArgumentError, _('The source topic and the target topic are identical.') if source_topic == target_topic
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def move_project_topics
      project_ids_for_projects_currently_using_source_and_target = ::Projects::ProjectTopic
        .where(topic_id: target_topic).select(:project_id)
      # Only update for projects that exclusively use the source topic
      ::Projects::ProjectTopic.where(topic_id: source_topic.id)
        .where.not(project_id: project_ids_for_projects_currently_using_source_and_target)
        .update_all(topic_id: target_topic.id)

      # Delete source topic for projects that were using source and target
      ::Projects::ProjectTopic.where(topic_id: source_topic.id).delete_all
    end

    def refresh_target_topic_counters
      target_topic.update!(
        total_projects_count: total_projects_count(target_topic.id),
        non_private_projects_count: non_private_projects_count(target_topic.id)
      )
    end

    def delete_source_topic
      source_topic.destroy!
    end

    def total_projects_count(topic_id)
      ::Projects::ProjectTopic.where(topic_id: topic_id).count
    end

    def non_private_projects_count(topic_id)
      ::Projects::ProjectTopic.joins(:project).where(topic_id: topic_id).where('projects.visibility_level in (10, 20)')
        .count
    end
    # rubocop: enable CodeReuse/ActiveRecord
  end
end