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
|
# frozen_string_literal: true
class AuditEvent < ApplicationRecord
include AfterCommitQueue
include CreatedAtFilterable
include BulkInsertSafe
include EachBatch
include PartitionedTable
PARALLEL_PERSISTENCE_COLUMNS = [
:author_name,
:entity_path,
:target_details,
:target_type,
:target_id
].freeze
self.primary_key = :id
partitioned_by :created_at, strategy: :monthly
serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user, foreign_key: :author_id, inverse_of: :audit_events
validates :author_id, presence: true
validates :entity_id, presence: true
validates :entity_type, presence: true
validates :ip_address, ip_address: true
scope :by_entity_type, ->(entity_type) { where(entity_type: entity_type) }
scope :by_entity_id, ->(entity_id) { where(entity_id: entity_id) }
scope :by_author_id, ->(author_id) { where(author_id: author_id) }
scope :by_entity_username, ->(username) { where(entity_id: find_user_id(username)) }
scope :by_author_username, ->(username) { where(author_id: find_user_id(username)) }
after_initialize :initialize_details
before_validation :sanitize_message
# Note: The intention is to remove this once refactoring of AuditEvent
# has proceeded further.
#
# See further details in the epic:
# https://gitlab.com/groups/gitlab-org/-/epics/2765
after_validation :parallel_persist
def self.supported_keyset_orderings
{ id: [:desc] }
end
def self.order_by(method)
case method.to_s
when 'created_asc'
order(id: :asc)
else
order(id: :desc)
end
end
def initialize_details
return unless has_attribute?(:details)
self.details = {} if details&.nil?
end
def author_name
author&.name
end
def formatted_details
details
.merge(details.slice(:from, :to).transform_values(&:to_s))
.merge(author_email: author.try(:email))
end
def author
lazy_author&.itself.presence || default_author_value
end
def lazy_author
BatchLoader.for(author_id).batch do |author_ids, loader|
User.select(:id, :name, :username, :email).where(id: author_ids).find_each do |user|
loader.call(user.id, user)
end
end
end
def as_json(options = {})
super(options).tap do |json|
json['ip_address'] = ip_address.to_s
end
end
def target_type
super || details[:target_type]
end
def target_id
details[:target_id]
end
def target_details
super || details[:target_details]
end
private
def sanitize_message
message = details[:custom_message]
return unless message
self.details = details.merge(custom_message: Sanitize.clean(message))
end
def default_author_value
::Gitlab::Audit::NullAuthor.for(author_id, self)
end
def parallel_persist
PARALLEL_PERSISTENCE_COLUMNS.each do |name|
original = self[name] || details[name]
next unless original
self[name] = details[name] = original
end
end
def self.find_user_id(username)
User.find_by_username(username)&.id
end
end
AuditEvent.prepend_mod_with('AuditEvent')
|