Commit e02ff958 authored by syasonik's avatar syasonik

Add worker to create sentry link to gitlab issues

Adds a worker class which can be asyncronously called that
creates a link between a GitLab issue and a Sentry issue in
Sentry via their API. The worker is queued only on creation of
a SentryIssue.
parent 5cd08b0e
......@@ -73,7 +73,9 @@ module ErrorTracking
end
def sentry_client
Sentry::Client.new(api_url, token)
strong_memoize(:sentry_client) do
Sentry::Client.new(api_url, token)
end
end
def sentry_external_url
......
......@@ -6,9 +6,15 @@ class SentryIssue < ApplicationRecord
validates :issue, uniqueness: true, presence: true
validates :sentry_issue_identifier, presence: true
after_create_commit :enqueue_sentry_sync_job
def self.for_project_and_identifier(project, identifier)
joins(:issue)
.where(issues: { project_id: project.id })
.find_by_sentry_issue_identifier(identifier)
end
def enqueue_sentry_sync_job
ErrorTrackingIssueLinkWorker.perform_async(issue.id)
end
end
......@@ -143,6 +143,7 @@
- delete_user
- email_receiver
- emails_on_push
- error_tracking_issue_link
- expire_build_instance_artifacts
- git_garbage_collect
- gitlab_shell
......
# frozen_string_literal: true
# Creates a link in Sentry between a Sentry issue and a GitLab issue.
# If the link already exists, no changes will occur.
# If a link to a different GitLab issue exists, a new link
# will still be created, but will not be visible in Sentry
# until the prior link is deleted.
class ErrorTrackingIssueLinkWorker
include ApplicationWorker
include ExclusiveLeaseGuard
include Gitlab::Utils::StrongMemoize
feature_category :error_tracking
worker_has_external_dependencies!
LEASE_TIMEOUT = 15.minutes
attr_reader :issue
def perform(issue_id)
@issue = Issue.find_by_id(issue_id)
return unless issue && error_tracking && sentry_issue_id
try_obtain_lease do
logger.info("Linking Sentry issue #{sentry_issue_id} to GitLab issue #{issue.id}")
if integration_id.nil?
logger.info("Sentry integration unavailable for #{error_tracking.api_url}")
break
end
sentry_client.create_issue_link(integration_id, sentry_issue_id, issue)
rescue Sentry::Client::Error
logger.info("Failed to link Sentry issue #{sentry_issue_id} to GitLab issue #{issue.id}")
end
end
private
def error_tracking
strong_memoize(:error_tracking) do
issue.project.error_tracking_setting
end
end
def sentry_issue_id
strong_memoize(:sentry_issue_id) do
issue.sentry_issue.sentry_issue_identifier
end
end
def sentry_client
error_tracking.sentry_client
end
def integration_id
strong_memoize(:integration_id) do
repo&.integration_id
end
end
def repo
sentry_client
.repos(organization_slug)
.find { |repo| repo.project_id == issue.project_id && repo.status == 'active' }
end
def organization_slug
error_tracking.organization_slug
end
def project_url
::Gitlab::Routing.url_helpers.project_url(issue.project)
end
def lease_key
"link_sentry_issue_#{sentry_issue_id}_gitlab_#{issue.id}"
end
def lease_timeout
LEASE_TIMEOUT
end
end
---
title: Sync GitLab issue back to Sentry when created in GitLab
merge_request: 23007
author:
type: added
......@@ -101,6 +101,7 @@
- [group_export, 1]
- [self_monitoring_project_create, 2]
- [self_monitoring_project_delete, 2]
- [error_tracking_issue_link, 2]
# EE-specific queues
- [analytics, 1]
......
......@@ -15,6 +15,18 @@ describe SentryIssue do
it { is_expected.to validate_presence_of(:sentry_issue_identifier) }
end
describe 'callbacks' do
context 'after create commit do' do
it 'updates Sentry with a reciprocal link on creation' do
issue = create(:issue)
expect(ErrorTrackingIssueLinkWorker).to receive(:perform_async).with(issue.id)
create(:sentry_issue, issue: issue)
end
end
end
describe '.for_project_and_identifier' do
let!(:sentry_issue) { create(:sentry_issue) }
let(:project) { sentry_issue.issue.project }
......
# frozen_string_literal: true
require 'spec_helper'
describe ErrorTrackingIssueLinkWorker do
let_it_be(:error_tracking) { create(:project_error_tracking_setting) }
let_it_be(:project) { error_tracking.project }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:sentry_issue) { create(:sentry_issue, issue: issue) }
let(:repo) do
Gitlab::ErrorTracking::Repo.new(
status: 'active',
integration_id: 66666,
project_id: project.id
)
end
subject { described_class.new.perform(issue.id) }
describe '#perform' do
it 'creates a link between an issue and a Sentry issue in Sentry' do
expect_next_instance_of(Sentry::Client) do |client|
expect(client).to receive(:repos).with('sentry-org').and_return([repo])
expect(client)
.to receive(:create_issue_link)
.with(66666, sentry_issue.sentry_issue_identifier, issue)
.and_return(true)
end
expect(subject).to be true
end
shared_examples_for 'makes no external API requests' do
it 'takes no action' do
expect_any_instance_of(Sentry::Client).not_to receive(:repos)
expect_any_instance_of(Sentry::Client).not_to receive(:create_issue_link)
expect(subject).to be nil
end
end
shared_examples_for 'terminates after one API request' do
it 'takes no action' do
expect_next_instance_of(Sentry::Client) do |client|
expect(client).to receive(:repos).with('sentry-org').and_return([repo])
end
expect_any_instance_of(Sentry::Client).not_to receive(:create_issue_link)
expect(subject).to be nil
end
end
context 'when issue is unavailable' do
let(:issue) { double('issue', id: -3) }
it_behaves_like 'makes no external API requests'
end
context 'when project does not have error tracking configured' do
let(:issue) { build(:project) }
it_behaves_like 'makes no external API requests'
end
context 'when the issue is not linked to a Sentry issue in GitLab' do
let(:issue) { build(:issue, project: project) }
it_behaves_like 'makes no external API requests'
end
context 'when Sentry disabled the GitLab integration' do
let(:repo) do
Gitlab::ErrorTracking::Repo.new(
status: 'inactive',
integration_id: 66666,
project_id: project.id
)
end
it_behaves_like 'terminates after one API request'
end
context 'when Sentry the GitLab integration is for another project' do
let(:repo) do
Gitlab::ErrorTracking::Repo.new(
status: 'active',
integration_id: 66666,
project_id: -3
)
end
it_behaves_like 'terminates after one API request'
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