Commit 734f4d93 authored by syasonik's avatar syasonik Committed by Douglas Barbosa Alexandre

Add support to create issue links via sentry client

Adds methods for two endpoints to the Sentry client. These include
logic to expose the integration id required to create a link to a gitlab
issue, and logic to create a link between a GitLab issue and a Sentry
issue in Sentry via their API.
parent cb19cc45
# frozen_string_literal: true
module Gitlab
module ErrorTracking
class Repo
attr_accessor :status, :integration_id, :project_id
def initialize(status:, integration_id:, project_id:)
@status = status
@integration_id = integration_id
@project_id = project_id
end
end
end
end
...@@ -5,6 +5,8 @@ module Sentry ...@@ -5,6 +5,8 @@ module Sentry
include Sentry::Client::Event include Sentry::Client::Event
include Sentry::Client::Projects include Sentry::Client::Projects
include Sentry::Client::Issue include Sentry::Client::Issue
include Sentry::Client::Repo
include Sentry::Client::IssueLink
Error = Class.new(StandardError) Error = Class.new(StandardError)
MissingKeysError = Class.new(StandardError) MissingKeysError = Class.new(StandardError)
...@@ -79,7 +81,7 @@ module Sentry ...@@ -79,7 +81,7 @@ module Sentry
end end
def handle_response(response) def handle_response(response)
unless response.code == 200 unless response.code.between?(200, 204)
raise_error "Sentry response status code: #{response.code}" raise_error "Sentry response status code: #{response.code}"
end end
......
# frozen_string_literal: true
module Sentry
class Client
module IssueLink
def create_issue_link(integration_id, sentry_issue_identifier, issue)
issue_link_url = issue_link_api_url(integration_id, sentry_issue_identifier)
params = {
project: issue.project.id,
externalIssue: "#{issue.project.id}##{issue.iid}"
}
http_put(issue_link_url, params)
end
private
def issue_link_api_url(integration_id, sentry_issue_identifier)
issue_link_url = URI(url)
issue_link_url.path = "/api/0/groups/#{sentry_issue_identifier}/integrations/#{integration_id}/"
issue_link_url
end
end
end
end
# frozen_string_literal: true
module Sentry
class Client
module Repo
def repos(organization_slug)
repos_url = repos_api_url(organization_slug)
repos = http_get(repos_url)[:body]
handle_mapping_exceptions do
map_to_repos(repos)
end
end
private
def repos_api_url(organization_slug)
repos_url = URI(url)
repos_url.path = "/api/0/organizations/#{organization_slug}/repos/"
repos_url
end
def map_to_repos(repos)
repos.map(&method(:map_to_repo))
end
def map_to_repo(repo)
Gitlab::ErrorTracking::Repo.new(
status: repo.fetch('status'),
integration_id: repo.fetch('integrationId'),
project_id: repo.fetch('externalSlug')
)
end
end
end
end
{
"url": "https://gitlab.com/test/tanuki-inc/issues/3",
"integrationId": 44444,
"displayName": "test/tanuki-inc#3",
"id": 140319,
"key": "gitlab.com/test:test/tanuki-inc#3"
}
[
{
"status": "active",
"integrationId": "48066",
"externalSlug": 139,
"name": "test / tanuki-inc",
"provider": {
"id": "integrations:gitlab",
"name": "Gitlab"
},
"url": "https://gitlab.com/test/tanuki-inc",
"id": "52480",
"dateCreated": "2020-01-08T21:15:17.181520Z"
}
]
# frozen_string_literal: true
require 'spec_helper'
describe Sentry::Client::IssueLink do
include SentryClientHelpers
let(:error_tracking_setting) { create(:project_error_tracking_setting, api_url: sentry_url) }
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
let(:client) { error_tracking_setting.sentry_client }
let(:issue_link_sample_response) { JSON.parse(fixture_file('sentry/issue_link_sample_response.json')) }
describe '#create_issue_link' do
let(:integration_id) { 44444 }
let(:sentry_issue_id) { 11111111 }
let(:issue) { create(:issue, project: error_tracking_setting.project) }
let(:sentry_issue_link_url) { "https://sentrytest.gitlab.com/api/0/groups/#{sentry_issue_id}/integrations/#{integration_id}/" }
let(:sentry_api_response) { issue_link_sample_response }
let!(:sentry_api_request) { stub_sentry_request(sentry_issue_link_url, :put, body: sentry_api_response, status: 201) }
subject { client.create_issue_link(integration_id, sentry_issue_id, issue) }
it_behaves_like 'calls sentry api'
it { is_expected.to be_present }
context 'redirects' do
let(:sentry_api_url) { sentry_issue_link_url }
it_behaves_like 'no Sentry redirects', :put
end
context 'when exception is raised' do
let(:sentry_request_url) { sentry_issue_link_url }
it_behaves_like 'maps Sentry exceptions', :put
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Sentry::Client::Repo do
include SentryClientHelpers
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
let(:token) { 'test-token' }
let(:client) { Sentry::Client.new(sentry_url, token) }
let(:repos_sample_response) { JSON.parse(fixture_file('sentry/repos_sample_response.json')) }
describe '#repos' do
let(:organization_slug) { 'gitlab' }
let(:sentry_repos_url) { "https://sentrytest.gitlab.com/api/0/organizations/#{organization_slug}/repos/" }
let(:sentry_api_response) { repos_sample_response }
let!(:sentry_api_request) { stub_sentry_request(sentry_repos_url, body: sentry_api_response) }
subject { client.repos(organization_slug) }
it_behaves_like 'calls sentry api'
it { is_expected.to all( be_a(Gitlab::ErrorTracking::Repo)) }
it { expect(subject.length).to eq(1) }
context 'redirects' do
let(:sentry_api_url) { sentry_repos_url }
it_behaves_like 'no Sentry redirects'
end
context 'when exception is raised' do
let(:sentry_request_url) { sentry_repos_url }
it_behaves_like 'maps Sentry exceptions'
end
end
end
...@@ -12,4 +12,6 @@ describe Sentry::Client do ...@@ -12,4 +12,6 @@ describe Sentry::Client do
it { is_expected.to respond_to :list_issues } it { is_expected.to respond_to :list_issues }
it { is_expected.to respond_to :issue_details } it { is_expected.to respond_to :issue_details }
it { is_expected.to respond_to :issue_latest_event } it { is_expected.to respond_to :issue_latest_event }
it { is_expected.to respond_to :repos }
it { is_expected.to respond_to :create_issue_link }
end end
...@@ -10,7 +10,7 @@ RSpec.shared_examples 'calls sentry api' do ...@@ -10,7 +10,7 @@ RSpec.shared_examples 'calls sentry api' do
end end
# Requires sentry_api_url and subject to be defined # Requires sentry_api_url and subject to be defined
RSpec.shared_examples 'no Sentry redirects' do RSpec.shared_examples 'no Sentry redirects' do |http_method|
let(:redirect_to) { 'https://redirected.example.com' } let(:redirect_to) { 'https://redirected.example.com' }
let(:other_url) { 'https://other.example.org' } let(:other_url) { 'https://other.example.org' }
...@@ -19,6 +19,7 @@ RSpec.shared_examples 'no Sentry redirects' do ...@@ -19,6 +19,7 @@ RSpec.shared_examples 'no Sentry redirects' do
let!(:redirect_req_stub) do let!(:redirect_req_stub) do
stub_sentry_request( stub_sentry_request(
sentry_api_url, sentry_api_url,
http_method || :get,
status: 302, status: 302,
headers: { location: redirect_to } headers: { location: redirect_to }
) )
...@@ -31,7 +32,7 @@ RSpec.shared_examples 'no Sentry redirects' do ...@@ -31,7 +32,7 @@ RSpec.shared_examples 'no Sentry redirects' do
end end
end end
RSpec.shared_examples 'maps Sentry exceptions' do RSpec.shared_examples 'maps Sentry exceptions' do |http_method|
exceptions = { exceptions = {
Gitlab::HTTP::Error => 'Error when connecting to Sentry', Gitlab::HTTP::Error => 'Error when connecting to Sentry',
Net::OpenTimeout => 'Connection to Sentry timed out', Net::OpenTimeout => 'Connection to Sentry timed out',
...@@ -44,7 +45,10 @@ RSpec.shared_examples 'maps Sentry exceptions' do ...@@ -44,7 +45,10 @@ RSpec.shared_examples 'maps Sentry exceptions' do
exceptions.each do |exception, message| exceptions.each do |exception, message|
context "#{exception}" do context "#{exception}" do
before do before do
stub_request(:get, sentry_request_url).to_raise(exception) stub_request(
http_method || :get,
sentry_request_url
).to_raise(exception)
end end
it do it do
......
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