summaryrefslogtreecommitdiff
path: root/lib/gitlab/ci/tags/bulk_insert.rb
blob: a299df7e2d9f6974128744add844c875f89b2dd4 (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
# frozen_string_literal: true

module Gitlab
  module Ci
    module Tags
      class BulkInsert
        TAGGINGS_BATCH_SIZE = 1000
        TAGS_BATCH_SIZE = 500

        def initialize(statuses, tag_list_by_status)
          @statuses = statuses
          @tag_list_by_status = tag_list_by_status
        end

        def insert!
          return false if tag_list_by_status.empty?

          persist_build_tags!
        end

        private

        attr_reader :statuses, :tag_list_by_status

        def persist_build_tags!
          all_tags = tag_list_by_status.values.flatten.uniq.reject(&:blank?)
          tag_records_by_name = create_tags(all_tags).index_by(&:name)
          taggings = build_taggings_attributes(tag_records_by_name)

          return false if taggings.empty?

          taggings.each_slice(TAGGINGS_BATCH_SIZE) do |taggings_slice|
            ActsAsTaggableOn::Tagging.insert_all!(taggings)
          end

          true
        end

        # rubocop: disable CodeReuse/ActiveRecord
        def create_tags(tags)
          existing_tag_records = ActsAsTaggableOn::Tag.where(name: tags).to_a
          missing_tags = detect_missing_tags(tags, existing_tag_records)
          return existing_tag_records if missing_tags.empty?

          missing_tags
            .map { |tag| { name: tag } }
            .each_slice(TAGS_BATCH_SIZE) do |tags_attributes|
              ActsAsTaggableOn::Tag.insert_all!(tags_attributes)
            end

          ActsAsTaggableOn::Tag.where(name: tags).to_a
        end
        # rubocop: enable CodeReuse/ActiveRecord

        def build_taggings_attributes(tag_records_by_name)
          taggings = statuses.flat_map do |status|
            tag_list = tag_list_by_status[status.name]
            next unless tag_list

            tags = tag_records_by_name.values_at(*tag_list)
            taggings_for(tags, status)
          end

          taggings.compact!
          taggings
        end

        def taggings_for(tags, status)
          tags.map do |tag|
            {
              tag_id: tag.id,
              taggable_type: CommitStatus.name,
              taggable_id: status.id,
              created_at: Time.current,
              context: 'tags'
            }
          end
        end

        def detect_missing_tags(tags, tag_records)
          if tags.size != tag_records.size
            tags - tag_records.map(&:name)
          else
            []
          end
        end
      end
    end
  end
end