summaryrefslogtreecommitdiff
path: root/app/services/admin/propagate_integration_service.rb
blob: 084b103ee3bc3a85aaa6e7d70c7bcedbae90b258 (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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# frozen_string_literal: true

module Admin
  class PropagateIntegrationService
    BATCH_SIZE = 100

    delegate :data_fields_present?, to: :integration

    def self.propagate(integration:, overwrite:)
      new(integration, overwrite).propagate
    end

    def initialize(integration, overwrite)
      @integration = integration
      @overwrite = overwrite
    end

    def propagate
      if overwrite
        update_integration_for_all_projects
      else
        update_integration_for_inherited_projects
      end

      create_integration_for_projects_without_integration
    end

    private

    attr_reader :integration, :overwrite

    # rubocop: disable Cop/InBatches
    # rubocop: disable CodeReuse/ActiveRecord
    def update_integration_for_inherited_projects
      Service.where(type: integration.type, inherit_from_id: integration.id).in_batches(of: BATCH_SIZE) do |batch|
        bulk_update_from_integration(batch)
      end
    end

    def update_integration_for_all_projects
      Service.where(type: integration.type).in_batches(of: BATCH_SIZE) do |batch|
        bulk_update_from_integration(batch)
      end
    end
    # rubocop: enable Cop/InBatches
    # rubocop: enable CodeReuse/ActiveRecord

    # rubocop: disable CodeReuse/ActiveRecord
    def bulk_update_from_integration(batch)
      # Retrieving the IDs instantiates the ActiveRecord relation (batch)
      # into concrete models, otherwise update_all will clear the relation.
      # https://stackoverflow.com/q/34811646/462015
      batch_ids = batch.pluck(:id)

      Service.transaction do
        batch.update_all(service_hash)

        if data_fields_present?
          integration.data_fields.class.where(service_id: batch_ids).update_all(data_fields_hash)
        end
      end
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def create_integration_for_projects_without_integration
      loop do
        batch = Project.uncached { project_ids_without_integration }

        bulk_create_from_integration(batch) unless batch.empty?

        break if batch.size < BATCH_SIZE
      end
    end

    def bulk_create_from_integration(batch)
      service_list = ServiceList.new(batch, service_hash, { 'inherit_from_id' => integration.id }).to_array

      Project.transaction do
        results = bulk_insert(*service_list)

        if data_fields_present?
          data_list = DataList.new(results, data_fields_hash, integration.data_fields.class).to_array

          bulk_insert(*data_list)
        end

        run_callbacks(batch)
      end
    end

    def bulk_insert(klass, columns, values_array)
      items_to_insert = values_array.map { |array| Hash[columns.zip(array)] }

      klass.insert_all(items_to_insert, returning: [:id])
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def run_callbacks(batch)
      if active_external_issue_tracker?
        Project.where(id: batch).update_all(has_external_issue_tracker: true)
      end

      if active_external_wiki?
        Project.where(id: batch).update_all(has_external_wiki: true)
      end
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def active_external_issue_tracker?
      integration.issue_tracker? && !integration.default
    end

    def active_external_wiki?
      integration.type == 'ExternalWikiService'
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def project_ids_without_integration
      services = Service
        .select('1')
        .where('services.project_id = projects.id')
        .where(type: integration.type)

      Project
        .where('NOT EXISTS (?)', services)
        .where(pending_delete: false)
        .where(archived: false)
        .limit(BATCH_SIZE)
        .pluck(:id)
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def service_hash
      @service_hash ||= integration.to_service_hash
        .tap { |json| json['inherit_from_id'] = integration.id }
    end

    def data_fields_hash
      @data_fields_hash ||= integration.to_data_fields_hash
    end
  end
end