Commit ec870938 authored by Sean Arnold's avatar Sean Arnold

Add Sentry error stack trace

- Create stack trace classes, including entry and context
- Add to specs to ensure resolvers and types work
parent cee39799
# frozen_string_literal: true
module Resolvers
module ErrorTracking
class SentryErrorStackTraceResolver < BaseResolver
argument :id, GraphQL::ID_TYPE,
required: true,
description: 'ID of the Sentry issue'
def resolve(**args)
current_user = context[:current_user]
issue_id = GlobalID.parse(args[:id]).model_id
# Get data from Sentry
response = ::ErrorTracking::IssueLatestEventService.new(
project,
current_user,
{ issue_id: issue_id }
).execute
event = response[:latest_event]
event.gitlab_project = project if event
event
end
private
def project
return object.gitlab_project if object.respond_to?(:gitlab_project)
object
end
end
end
end
......@@ -28,6 +28,10 @@ module Types
null: true,
description: 'Detailed version of a Sentry error on the project',
resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver
field :error_stack_trace, Types::ErrorTracking::SentryErrorStackTraceType,
null: true,
description: 'Stack Trace of Sentry Error',
resolver: Resolvers::ErrorTracking::SentryErrorStackTraceResolver
field :external_url,
GraphQL::STRING_TYPE,
null: true,
......
# frozen_string_literal: true
module Types
module ErrorTracking
# rubocop: disable Graphql/AuthorizeTypes
class SentryErrorStackTraceContextType < ::Types::BaseObject
graphql_name 'SentryErrorStackTraceContext'
description 'An object context for a Sentry error stack trace'
field :line,
GraphQL::INT_TYPE,
null: false,
description: 'Line number of the context'
field :code,
GraphQL::STRING_TYPE,
null: false,
description: 'Code number of the context'
def line
object[0]
end
def code
object[1]
end
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
# frozen_string_literal: true
module Types
module ErrorTracking
# rubocop: disable Graphql/AuthorizeTypes
class SentryErrorStackTraceEntryType < ::Types::BaseObject
graphql_name 'SentryErrorStackTraceEntry'
description 'An object containing a stack trace entry for a Sentry error.'
field :function, GraphQL::STRING_TYPE,
null: true,
description: 'Function in which the Sentry error occurred'
field :col, GraphQL::STRING_TYPE,
null: true,
description: 'Function in which the Sentry error occurred'
field :line, GraphQL::STRING_TYPE,
null: true,
description: 'Function in which the Sentry error occurred'
field :file_name, GraphQL::STRING_TYPE,
null: true,
description: 'File in which the Sentry error occurred'
field :trace_context, [Types::ErrorTracking::SentryErrorStackTraceContextType],
null: true,
description: 'Context of the Sentry error'
def function
object['function']
end
def col
object['colNo']
end
def line
object['lineNo']
end
def file_name
object['filename']
end
def trace_context
object['context']
end
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
# frozen_string_literal: true
module Types
module ErrorTracking
# rubocop: disable Graphql/AuthorizeTypes
class SentryErrorStackTraceType < ::Types::BaseObject
graphql_name 'SentryErrorStackTrace'
description 'An object containing a stack trace entry for a Sentry error.'
field :issue_id, GraphQL::STRING_TYPE,
null: false,
description: 'ID of the Sentry error'
field :date_received, GraphQL::STRING_TYPE,
null: false,
description: 'Time the stack trace was received by Sentry'
field :stack_trace_entries, [Types::ErrorTracking::SentryErrorStackTraceEntryType],
null: false,
description: 'Stack trace entries for the Sentry error'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
......@@ -5,7 +5,11 @@ module Gitlab
class ErrorEvent
include ActiveModel::Model
attr_accessor :issue_id, :date_received, :stack_trace_entries
attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project
def self.declarative_policy_class
'ErrorTracking::BasePolicy'
end
end
end
end
......@@ -12,6 +12,7 @@ describe GitlabSchema.types['SentryErrorCollection'] do
errors
detailed_error
external_url
errorStackTrace
]
is_expected.to have_graphql_fields(*expected_fields)
......
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['SentryErrorStackTraceEntry'] do
it { expect(described_class.graphql_name).to eq('SentryErrorStackTraceEntry') }
it 'exposes the expected fields' do
expected_fields = %i[
function
col
line
file_name
trace_context
]
is_expected.to have_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['SentryErrorStackTrace'] do
it { expect(described_class.graphql_name).to eq('SentryErrorStackTrace') }
it 'exposes the expected fields' do
expected_fields = %i[
issue_id
date_received
stack_trace_entries
]
is_expected.to have_graphql_fields(*expected_fields)
end
end
......@@ -188,4 +188,69 @@ describe 'sentry errors requests' do
end
end
end
describe 'getting a stack trace' do
let_it_be(:sentry_stack_trace) { build(:error_tracking_error_event) }
let(:sentry_gid) { Gitlab::ErrorTracking::DetailedError.new(id: 1).to_global_id.to_s }
let(:stack_trace_fields) do
all_graphql_fields_for('SentryErrorStackTrace'.classify)
end
let(:fields) do
query_graphql_field('errorStackTrace', { id: sentry_gid }, stack_trace_fields)
end
let(:stack_trace_data) { graphql_data.dig('project', 'sentryErrors', 'errorStackTrace') }
it_behaves_like 'a working graphql query' do
before do
post_graphql(query, current_user: current_user)
end
end
context 'when data is loading via reactive cache' do
before do
post_graphql(query, current_user: current_user)
end
it "is expected to return an empty error" do
expect(stack_trace_data).to eq nil
end
end
context 'reactive cache returns data' do
before do
allow_any_instance_of(ErrorTracking::ProjectErrorTrackingSetting)
.to receive(:issue_latest_event)
.and_return({ latest_event: sentry_stack_trace })
post_graphql(query, current_user: current_user)
end
it_behaves_like 'setting stack trace error'
context 'user does not have permission' do
let(:current_user) { create(:user) }
it "is expected to return an empty error" do
expect(stack_trace_data).to eq nil
end
end
end
context 'sentry api returns an error' do
before do
expect_any_instance_of(ErrorTracking::ProjectErrorTrackingSetting)
.to receive(:issue_latest_event)
.and_return({ error: 'error message' })
post_graphql(query, current_user: current_user)
end
it 'is expected to handle the error and return nil' do
expect(stack_trace_data).to eq nil
end
end
end
end
......@@ -11,3 +11,26 @@ RSpec.shared_examples 'setting sentry error data' do
end
end
end
RSpec.shared_examples 'setting stack trace error' do
it 'sets the stack trace data correctly' do
aggregate_failures 'testing the stack trace is correct' do
expect(stack_trace_data['dateReceived']).to eql(sentry_stack_trace.date_received)
expect(stack_trace_data['issueId']).to eql(sentry_stack_trace.issue_id)
expect(stack_trace_data['stackTraceEntries']).to be_an_instance_of(Array)
expect(stack_trace_data['stackTraceEntries'].size).to eql(sentry_stack_trace.stack_trace_entries.size)
end
end
it 'sets the stack trace entry data correctly' do
aggregate_failures 'testing the stack trace entry is correct' do
stack_trace_entry = stack_trace_data['stackTraceEntries'].first
model_entry = sentry_stack_trace.stack_trace_entries.first
expect(stack_trace_entry['function']).to eql model_entry['function']
expect(stack_trace_entry['col']).to eql model_entry['colNo']
expect(stack_trace_entry['line']).to eql model_entry['lineNo'].to_s
expect(stack_trace_entry['fileName']).to eql model_entry['filename']
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment