2021-11-25 15:09:54 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Gitlab
|
|
|
|
module Ci
|
|
|
|
module Tags
|
|
|
|
class BulkInsert
|
2022-01-13 15:14:46 +00:00
|
|
|
include Gitlab::Utils::StrongMemoize
|
|
|
|
|
2021-11-25 15:09:54 +00:00
|
|
|
TAGGINGS_BATCH_SIZE = 1000
|
|
|
|
TAGS_BATCH_SIZE = 500
|
|
|
|
|
2022-06-28 12:09:11 +00:00
|
|
|
def self.bulk_insert_tags!(taggables)
|
|
|
|
Gitlab::Ci::Tags::BulkInsert.new(taggables).insert!
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(taggables)
|
|
|
|
@taggables = taggables
|
2021-11-25 15:09:54 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def insert!
|
2022-06-28 12:09:11 +00:00
|
|
|
return false if tag_list_by_taggable.empty?
|
2021-11-25 15:09:54 +00:00
|
|
|
|
|
|
|
persist_build_tags!
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2022-06-28 12:09:11 +00:00
|
|
|
attr_reader :taggables
|
2022-01-13 15:14:46 +00:00
|
|
|
|
2022-06-28 12:09:11 +00:00
|
|
|
def tag_list_by_taggable
|
|
|
|
strong_memoize(:tag_list_by_taggable) do
|
|
|
|
taggables.each.with_object({}) do |taggable, acc|
|
|
|
|
tag_list = taggable.tag_list
|
2022-01-13 15:14:46 +00:00
|
|
|
next unless tag_list
|
|
|
|
|
2022-06-28 12:09:11 +00:00
|
|
|
acc[taggable] = tag_list
|
2022-01-13 15:14:46 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2021-11-25 15:09:54 +00:00
|
|
|
|
|
|
|
def persist_build_tags!
|
2022-06-28 12:09:11 +00:00
|
|
|
all_tags = tag_list_by_taggable.values.flatten.uniq.reject(&:blank?)
|
2021-11-25 15:09:54 +00:00
|
|
|
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|
|
2022-06-23 12:09:30 +00:00
|
|
|
ActsAsTaggableOn::Tagging.insert_all!(taggings_slice)
|
2021-11-25 15:09:54 +00:00
|
|
|
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)
|
2022-06-28 12:09:11 +00:00
|
|
|
taggings = taggables.flat_map do |taggable|
|
|
|
|
tag_list = tag_list_by_taggable[taggable]
|
2021-11-25 15:09:54 +00:00
|
|
|
next unless tag_list
|
|
|
|
|
|
|
|
tags = tag_records_by_name.values_at(*tag_list)
|
2022-06-28 12:09:11 +00:00
|
|
|
taggings_for(tags, taggable)
|
2021-11-25 15:09:54 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
taggings.compact!
|
|
|
|
taggings
|
|
|
|
end
|
|
|
|
|
2022-06-28 12:09:11 +00:00
|
|
|
def taggings_for(tags, taggable)
|
2021-11-25 15:09:54 +00:00
|
|
|
tags.map do |tag|
|
|
|
|
{
|
|
|
|
tag_id: tag.id,
|
2022-06-28 12:09:11 +00:00
|
|
|
taggable_type: taggable.class.base_class.name,
|
|
|
|
taggable_id: taggable.id,
|
2021-11-25 15:09:54 +00:00
|
|
|
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
|