Class: RailsInformant::ErrorGroup

Inherits:
ApplicationRecord show all
Defined in:
app/models/rails_informant/error_group.rb

Constant Summary collapse

API_FIELDS =
%i[
  controller_action
  created_at
  duplicate_of_id
  error_class
  fingerprint
  first_backtrace_line
  first_seen_at
  fix_deployed_at
  fix_pr_url
  fix_sha
  id
  job_class
  last_seen_at
  message
  original_sha
  resolved_at
  severity
  status
  total_occurrences
  updated_at
].freeze
API_DETAIL_FIELDS =
(API_FIELDS + %i[
  last_notified_at
  last_occurrence_stored_at
  notes
]).freeze
VALID_TRANSITIONS =
{
  "duplicate" => %w[unresolved],
  "fix_pending" => %w[resolved unresolved],
  "ignored" => %w[unresolved],
  "resolved" => %w[unresolved],
  "unresolved" => %w[duplicate fix_pending ignored resolved]
}.freeze
MAX_DUPLICATE_CHAIN_DEPTH =
10

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.circular_duplicate_chain?(target_id, source_id) ⇒ Boolean

Returns:

  • (Boolean)


92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'app/models/rails_informant/error_group.rb', line 92

def self.circular_duplicate_chain?(target_id, source_id)
  return false unless source_id

  seen = Set.new([ target_id ])
  current_id = source_id

  MAX_DUPLICATE_CHAIN_DEPTH.times do
    return false unless current_id
    return true if seen.include?(current_id)
    seen << current_id
    current_id = where(id: current_id).pick(:duplicate_of_id)
  end

  true # depth exceeded, treat as circular
end

.find_or_create_for(fingerprint, attributes) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'app/models/rails_informant/error_group.rb', line 65

def find_or_create_for(fingerprint, attributes)
  now = attributes[:last_seen_at]
  if (group = find_by(fingerprint:))
    increment_counters group, now
  else
    group = create!(attributes.merge(fingerprint:))
  end
  group
rescue ActiveRecord::RecordNotUnique
  group = find_by!(fingerprint:)
  increment_counters group, now
  group
end

Instance Method Details

#circular_duplicate_chain?(original_id) ⇒ Boolean

Returns:

  • (Boolean)


108
109
110
# File 'app/models/rails_informant/error_group.rb', line 108

def circular_duplicate_chain?(original_id)
  self.class.circular_duplicate_chain?(original_id, duplicate_of_id)
end

#detect_regression!Object



133
134
135
136
137
138
139
140
141
142
143
# File 'app/models/rails_informant/error_group.rb', line 133

def detect_regression!
  return unless status == "resolved"

  changed = self.class.where(id:, status: "resolved").update_all(
    status: "unresolved", resolved_at: nil, fix_deployed_at: nil,
    fix_sha: nil, original_sha: nil, fix_pr_url: nil, updated_at: Time.current)
  return unless changed > 0

  assign_attributes status: "unresolved", resolved_at: nil, fix_deployed_at: nil,
    fix_sha: nil, original_sha: nil, fix_pr_url: nil
end

#mark_as_duplicate_of!(target_id) ⇒ Object



122
123
124
125
126
127
128
129
130
131
# File 'app/models/rails_informant/error_group.rb', line 122

def mark_as_duplicate_of!(target_id)
  raise RailsInformant::InvalidParameterError, "duplicate_of_id is required" unless target_id.present?

  target = self.class.find_by(id: target_id)
  raise RailsInformant::InvalidParameterError, "Target error group not found" unless target
  raise RailsInformant::InvalidParameterError, "Cannot mark as duplicate of itself" if target.id == id
  raise RailsInformant::InvalidParameterError, "Circular duplicate chain detected" if target.circular_duplicate_chain?(id)

  update!(status: "duplicate", duplicate_of: target)
end

#mark_as_fix_pending!(fix_sha:, original_sha:, fix_pr_url: nil) ⇒ Object



112
113
114
115
116
117
118
119
120
# File 'app/models/rails_informant/error_group.rb', line 112

def mark_as_fix_pending!(fix_sha:, original_sha:, fix_pr_url: nil)
  raise RailsInformant::InvalidParameterError, "fix_sha is required" unless fix_sha.present?
  raise RailsInformant::InvalidParameterError, "original_sha is required" unless original_sha.present?
  validate_sha_format! fix_sha
  validate_sha_format! original_sha
  validate_url_scheme! fix_pr_url if fix_pr_url.present?

  update!(status: "fix_pending", fix_sha:, original_sha:, fix_pr_url:)
end