Commit 342d5ff4 authored by Dmytro Zaporozhets's avatar Dmytro Zaporozhets

Merge branch '39825-close-issue-after-error-resolve' into 'master'

Close related issue after error resolve

See merge request gitlab-org/gitlab!22744
parents 7467e0ae f5707c27
...@@ -222,6 +222,7 @@ class ProjectPolicy < BasePolicy ...@@ -222,6 +222,7 @@ class ProjectPolicy < BasePolicy
enable :read_deployment enable :read_deployment
enable :read_merge_request enable :read_merge_request
enable :read_sentry_issue enable :read_sentry_issue
enable :update_sentry_issue
enable :read_prometheus enable :read_prometheus
end end
......
...@@ -7,7 +7,7 @@ module ErrorTracking ...@@ -7,7 +7,7 @@ module ErrorTracking
return unauthorized if unauthorized return unauthorized if unauthorized
begin begin
response = fetch response = perform
rescue Sentry::Client::Error => e rescue Sentry::Client::Error => e
return error(e.message, :bad_request) return error(e.message, :bad_request)
rescue Sentry::Client::MissingKeysError => e rescue Sentry::Client::MissingKeysError => e
...@@ -22,7 +22,7 @@ module ErrorTracking ...@@ -22,7 +22,7 @@ module ErrorTracking
private private
def fetch def perform
raise NotImplementedError, raise NotImplementedError,
"#{self.class} does not implement #{__method__}" "#{self.class} does not implement #{__method__}"
end end
...@@ -62,5 +62,9 @@ module ErrorTracking ...@@ -62,5 +62,9 @@ module ErrorTracking
def can_read? def can_read?
can?(current_user, :read_sentry_issue, project) can?(current_user, :read_sentry_issue, project)
end end
def can_update?
can?(current_user, :update_sentry_issue, project)
end
end end
end end
...@@ -4,7 +4,7 @@ module ErrorTracking ...@@ -4,7 +4,7 @@ module ErrorTracking
class IssueDetailsService < ErrorTracking::BaseService class IssueDetailsService < ErrorTracking::BaseService
private private
def fetch def perform
project_error_tracking_setting.issue_details(issue_id: params[:issue_id]) project_error_tracking_setting.issue_details(issue_id: params[:issue_id])
end end
......
...@@ -4,7 +4,7 @@ module ErrorTracking ...@@ -4,7 +4,7 @@ module ErrorTracking
class IssueLatestEventService < ErrorTracking::BaseService class IssueLatestEventService < ErrorTracking::BaseService
private private
def fetch def perform
project_error_tracking_setting.issue_latest_event(issue_id: params[:issue_id]) project_error_tracking_setting.issue_latest_event(issue_id: params[:issue_id])
end end
......
...@@ -4,6 +4,16 @@ module ErrorTracking ...@@ -4,6 +4,16 @@ module ErrorTracking
class IssueUpdateService < ErrorTracking::BaseService class IssueUpdateService < ErrorTracking::BaseService
private private
def perform
response = fetch
unless parse_errors(response).present?
response[:closed_issue_iid] = update_related_issue&.iid
end
response
end
def fetch def fetch
project_error_tracking_setting.update_issue( project_error_tracking_setting.update_issue(
issue_id: params[:issue_id], issue_id: params[:issue_id],
...@@ -11,12 +21,58 @@ module ErrorTracking ...@@ -11,12 +21,58 @@ module ErrorTracking
) )
end end
def update_related_issue
issue = related_issue
return unless issue
close_and_create_note(issue)
end
def close_and_create_note(issue)
return unless resolving? && issue.opened?
processed_issue = close_issue(issue)
return unless processed_issue.reset.closed?
create_system_note(processed_issue)
processed_issue
end
def close_issue(issue)
Issues::CloseService
.new(project, current_user)
.execute(issue, system_note: false)
end
def create_system_note(issue)
SystemNoteService.close_after_error_tracking_resolve(issue, project, current_user)
end
def related_issue
SentryIssueFinder
.new(project, current_user: current_user)
.execute(params[:issue_id])
&.issue
end
def resolving?
update_params[:status] == 'resolved'
end
def update_params def update_params
params.except(:issue_id) params.except(:issue_id)
end end
def parse_response(response) def parse_response(response)
{ updated: response[:updated].present? } {
updated: response[:updated].present?,
closed_issue_iid: response[:closed_issue_iid]
}
end
def check_permissions
return error('Error Tracking is not enabled') unless enabled?
return error('Access denied', :unauthorized) unless can_update?
end end
end end
end end
...@@ -12,7 +12,7 @@ module ErrorTracking ...@@ -12,7 +12,7 @@ module ErrorTracking
private private
def fetch def perform
project_error_tracking_setting.list_sentry_issues( project_error_tracking_setting.list_sentry_issues(
issue_status: issue_status, issue_status: issue_status,
limit: limit, limit: limit,
......
...@@ -12,7 +12,7 @@ module ErrorTracking ...@@ -12,7 +12,7 @@ module ErrorTracking
private private
def fetch def perform
project_error_tracking_setting.list_sentry_projects project_error_tracking_setting.list_sentry_projects
end end
......
...@@ -99,6 +99,12 @@ module SystemNoteService ...@@ -99,6 +99,12 @@ module SystemNoteService
::SystemNotes::TimeTrackingService.new(noteable: noteable, project: project, author: author).change_time_spent ::SystemNotes::TimeTrackingService.new(noteable: noteable, project: project, author: author).change_time_spent
end end
def close_after_error_tracking_resolve(issue, project, author)
body = _('resolved the corresponding error and closed the issue.')
create_note(NoteSummary.new(issue, project, author, body, action: 'closed'))
end
def change_status(noteable, project, author, status, source = nil) def change_status(noteable, project, author, status, source = nil)
::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).change_status(status, source) ::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).change_status(status, source)
end end
......
---
title: Close Issue when resolving corresponding Sentry error
merge_request: 22744
author:
type: added
...@@ -22788,6 +22788,9 @@ msgstr[1] "" ...@@ -22788,6 +22788,9 @@ msgstr[1] ""
msgid "reset it." msgid "reset it."
msgstr "" msgstr ""
msgid "resolved the corresponding error and closed the issue."
msgstr ""
msgid "score" msgid "score"
msgstr "" msgstr ""
......
...@@ -301,7 +301,7 @@ describe Projects::ErrorTrackingController do ...@@ -301,7 +301,7 @@ describe Projects::ErrorTrackingController do
context 'update result is successful' do context 'update result is successful' do
before do before do
expect(issue_update_service).to receive(:execute) expect(issue_update_service).to receive(:execute)
.and_return(status: :success, updated: true) .and_return(status: :success, updated: true, closed_issue_iid: 1234)
update_issue update_issue
end end
......
...@@ -6,9 +6,15 @@ ...@@ -6,9 +6,15 @@
"properties" : { "properties" : {
"result": { "result": {
"type": "object", "type": "object",
"required" : [
"status",
"updated",
"closed_issue_iid"
],
"properties": { "properties": {
"status": { "type": "string" }, "status": { "type": "string" },
"updated": { "type": "boolean" } "updated": { "type": "boolean" },
"closed_issue_iid": { "type": ["integer", "null"] }
} }
} }
}, },
......
...@@ -3,24 +3,7 @@ ...@@ -3,24 +3,7 @@
require 'spec_helper' require 'spec_helper'
describe ErrorTracking::IssueDetailsService do describe ErrorTracking::IssueDetailsService do
let_it_be(:user) { create(:user) } include_context 'sentry error tracking context'
let_it_be(:project) { create(:project) }
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
let(:token) { 'test-token' }
let(:result) { subject.execute }
let(:error_tracking_setting) do
create(:project_error_tracking_setting, api_url: sentry_url, token: token, project: project)
end
subject { described_class.new(project, user) }
before do
expect(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting)
project.add_reporter(user)
end
describe '#execute' do describe '#execute' do
context 'with authorized user' do context 'with authorized user' do
......
...@@ -3,24 +3,7 @@ ...@@ -3,24 +3,7 @@
require 'spec_helper' require 'spec_helper'
describe ErrorTracking::IssueLatestEventService do describe ErrorTracking::IssueLatestEventService do
let_it_be(:user) { create(:user) } include_context 'sentry error tracking context'
let_it_be(:project) { create(:project) }
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
let(:token) { 'test-token' }
let(:result) { subject.execute }
let(:error_tracking_setting) do
create(:project_error_tracking_setting, api_url: sentry_url, token: token, project: project)
end
subject { described_class.new(project, user) }
before do
expect(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting)
project.add_reporter(user)
end
describe '#execute' do describe '#execute' do
context 'with authorized user' do context 'with authorized user' do
......
# frozen_string_literal: true
require 'spec_helper'
describe ErrorTracking::IssueUpdateService do
include_context 'sentry error tracking context'
let(:arguments) { { issue_id: 1234, status: 'resolved' } }
subject { described_class.new(project, user, arguments) }
shared_examples 'does not perform close issue flow' do
it 'does not call the close issue service' do
expect(issue_close_service)
.not_to receive(:execute)
end
it 'does not create system note' do
expect(SystemNoteService).not_to receive(:close_after_error_tracking_resolve)
end
end
describe '#execute' do
context 'with authorized user' do
context 'when update_issue returns success' do
let(:update_issue_response) { { updated: true } }
before do
expect(error_tracking_setting)
.to receive(:update_issue).and_return(update_issue_response)
end
it 'returns the response' do
expect(result).to eq(update_issue_response.merge(status: :success, closed_issue_iid: nil))
end
it 'updates any related issue' do
expect(subject).to receive(:update_related_issue)
result
end
context 'related issue and resolving' do
let(:issue) { create(:issue, project: project) }
let(:sentry_issue) { create(:sentry_issue, issue: issue) }
let(:arguments) { { issue_id: sentry_issue.sentry_issue_identifier, status: 'resolved' } }
let(:issue_close_service) { spy(:issue_close_service) }
before do
allow_any_instance_of(SentryIssueFinder)
.to receive(:execute)
.and_return(sentry_issue)
allow(Issues::CloseService)
.to receive(:new)
.and_return(issue_close_service)
end
after do
result
end
it 'closes the issue' do
expect(issue_close_service)
.to receive(:execute)
.with(issue, system_note: false)
.and_return(issue)
end
it 'creates a system note' do
expect(SystemNoteService).to receive(:close_after_error_tracking_resolve)
end
it 'returns a response with closed issue' do
closed_issue = create(:issue, :closed, project: project)
expect(issue_close_service)
.to receive(:execute)
.with(issue, system_note: false)
.and_return(closed_issue)
expect(result).to eq(status: :success, updated: true, closed_issue_iid: closed_issue.iid)
end
context 'issue is already closed' do
let(:issue) { create(:issue, :closed, project: project) }
include_examples 'does not perform close issue flow'
end
context 'status is not resolving' do
let(:arguments) { { issue_id: sentry_issue.sentry_issue_identifier, status: 'ignored' } }
include_examples 'does not perform close issue flow'
end
end
end
include_examples 'error tracking service sentry error handling', :update_issue
end
include_examples 'error tracking service unauthorized user'
include_examples 'error tracking service disabled'
end
end
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
require 'spec_helper' require 'spec_helper'
describe ErrorTracking::ListIssuesService do describe ErrorTracking::ListIssuesService do
let_it_be(:user) { create(:user) } include_context 'sentry error tracking context'
let_it_be(:project) { create(:project) }
let(:params) { { search_term: 'something', sort: 'last_seen', cursor: 'some-cursor' } } let(:params) { { search_term: 'something', sort: 'last_seen', cursor: 'some-cursor' } }
let(:list_sentry_issues_args) do let(:list_sentry_issues_args) do
{ {
...@@ -16,22 +16,8 @@ describe ErrorTracking::ListIssuesService do ...@@ -16,22 +16,8 @@ describe ErrorTracking::ListIssuesService do
} }
end end
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
let(:token) { 'test-token' }
let(:result) { subject.execute }
let(:error_tracking_setting) do
create(:project_error_tracking_setting, api_url: sentry_url, token: token, project: project)
end
subject { described_class.new(project, user, params) } subject { described_class.new(project, user, params) }
before do
expect(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting)
project.add_reporter(user)
end
describe '#execute' do describe '#execute' do
context 'with authorized user' do context 'with authorized user' do
context 'when list_sentry_issues returns issues' do context 'when list_sentry_issues returns issues' do
......
# frozen_string_literal: true
shared_context 'sentry error tracking context' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
let(:token) { 'test-token' }
let(:result) { subject.execute }
subject { described_class.new(project, user) }
let(:error_tracking_setting) do
create(:project_error_tracking_setting, api_url: sentry_url, token: token, project: project)
end
before do
expect(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting)
project.add_reporter(user)
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