summaryrefslogtreecommitdiff
path: root/lib/gitlab/error_tracking/error_repository/active_record_strategy.rb
blob: e5b532ee0f01dd8cab6b69e08c07ff3d4cf626f2 (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
# frozen_string_literal: true

module Gitlab
  module ErrorTracking
    class ErrorRepository
      class ActiveRecordStrategy
        def initialize(project)
          @project = project
        end

        def report_error(
          name:, description:, actor:, platform:,
          environment:, level:, occurred_at:, payload:
        )
          error = project_errors.report_error(
            name: name,                # Example: ActionView::MissingTemplate
            description: description,  # Example: Missing template posts/show in...
            actor: actor,              # Example: PostsController#show
            platform: platform,        # Example: ruby
            timestamp: occurred_at
          )

          # The payload field contains all the data on error including stacktrace in jsonb.
          # Together with occurred_at these are 2 main attributes that we need to save here.
          error.events.create!(
            environment: environment,
            description: description,
            level: level,
            occurred_at: occurred_at,
            payload: payload
          )
        rescue ActiveRecord::ActiveRecordError => e
          handle_exceptions(e)
        end

        def find_error(id)
          project_error(id).to_sentry_detailed_error
        rescue ActiveRecord::ActiveRecordError => e
          handle_exceptions(e)
        end

        def list_errors(filters:, sort:, limit:, cursor:)
          errors = project_errors
          errors = filter_by_status(errors, filters[:status])
          errors = sort(errors, sort)
          errors = errors.keyset_paginate(cursor: cursor, per_page: limit)

          pagination = ErrorRepository::Pagination.new(errors.cursor_for_next_page, errors.cursor_for_previous_page)

          [errors.map(&:to_sentry_error), pagination]
        end

        def last_event_for(id)
          project_error(id).last_event&.to_sentry_error_event
        rescue ActiveRecord::ActiveRecordError => e
          handle_exceptions(e)
        end

        def update_error(id, **attributes)
          project_error(id).update(attributes)
        end

        private

        attr_reader :project

        def project_errors
          ::ErrorTracking::Error.where(project: project) # rubocop:disable CodeReuse/ActiveRecord
        end

        def project_error(id)
          project_errors.find(id)
        end

        def filter_by_status(errors, status)
          return errors unless ::ErrorTracking::Error.statuses.key?(status)

          errors.for_status(status)
        end

        def sort(errors, sort)
          return errors.order_id_desc unless sort

          errors.sort_by_attribute(sort)
        end

        def handle_exceptions(exception)
          case exception
          when ActiveRecord::RecordInvalid
            raise RecordInvalidError, exception.message
          else
            raise DatabaseError, exception.message
          end
        end
      end
    end
  end
end