Commit e155625c authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ck3g-call-create-alert-issue-service-from-process-alert-worker' into 'master'

Call CreateAlertIssueService from ProcessAlertWorker

See merge request gitlab-org/gitlab!39334
parents 72a9663d 400ab005
# frozen_string_literal: true
module IncidentManagement
class CreateIssueService < BaseService
include Gitlab::Utils::StrongMemoize
include IncidentManagement::Settings
attr_reader :alert
def initialize(project, alert)
super(project, User.alert_bot)
@alert = alert
end
def execute
return error('setting disabled') unless incident_management_setting.create_issue?
return error('invalid alert') unless alert_presenter.valid?
result = create_incident
return error(result.message, result.payload[:issue]) unless result.success?
result
end
private
def create_incident
::IncidentManagement::Incidents::CreateService.new(
project,
current_user,
title: issue_title,
description: issue_description
).execute
end
def issue_title
alert_presenter.full_title
end
def issue_description
horizontal_line = "\n\n---\n\n"
[
alert_summary,
alert_markdown,
issue_template_content
].compact.join(horizontal_line)
end
def alert_summary
alert_presenter.issue_summary_markdown
end
def alert_markdown
alert_presenter.alert_markdown
end
def alert_presenter
strong_memoize(:alert_presenter) do
Gitlab::Alerting::Alert.for_alert_management_alert(project: project, alert: alert).present
end
end
def issue_template_content
incident_management_setting.issue_template_content
end
def error(message, issue = nil)
log_error(%{Cannot create incident issue for "#{project.full_name}": #{message}})
ServiceResponse.error(payload: { issue: issue }, message: message)
end
end
end
......@@ -17,10 +17,9 @@ module IncidentManagement
return unless alert
result = create_issue_for(alert)
return unless result.success?
return if result.success?
new_issue = result.payload[:issue]
link_issue_with_alert(alert, new_issue.id)
log_warning(alert, result)
end
private
......@@ -30,19 +29,19 @@ module IncidentManagement
end
def create_issue_for(alert)
IncidentManagement::CreateIssueService
.new(alert.project, alert)
AlertManagement::CreateAlertIssueService
.new(alert, User.alert_bot)
.execute
end
def link_issue_with_alert(alert, issue_id)
return if alert.update(issue_id: issue_id)
def log_warning(alert, result)
issue_id = result.payload[:issue]&.id
Gitlab::AppLogger.warn(
message: 'Cannot link an Issue with Alert',
message: 'Cannot process an Incident',
issue_id: issue_id,
alert_id: alert.id,
alert_errors: alert.errors.messages
errors: result.message
)
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::CreateIssueService do
let(:project) { create(:project, :repository, :private) }
let_it_be(:user) { User.alert_bot }
let(:alert_starts_at) { Time.current }
let(:alert_title) { 'TITLE' }
let(:alert_annotations) { { title: alert_title } }
let(:alert_payload) do
build_alert_payload(
annotations: alert_annotations,
starts_at: alert_starts_at
)
end
let(:alert) { create(:alert_management_alert, :prometheus, project: project, payload: alert_payload) }
let(:service) { described_class.new(project, alert) }
let(:alert_presenter) do
Gitlab::Alerting::Alert.for_alert_management_alert(project: project, alert: alert).present
end
let!(:setting) do
create(:project_incident_management_setting, project: project)
end
subject(:execute) { service.execute }
context 'when create_issue enabled' do
let(:issue) { execute.payload[:issue] }
before do
setting.update!(create_issue: true)
end
context 'without issue_template_content' do
it 'creates an issue with alert summary only' do
expect(execute).to be_success
expect(issue.author).to eq(user)
expect(issue.title).to eq(alert_title)
expect(issue.description).to include(alert_presenter.issue_summary_markdown.strip)
expect(separator_count(issue.description)).to eq(0)
end
end
context 'with erroneous issue service' do
let(:invalid_issue) do
build(:issue, project: project, title: nil).tap(&:valid?)
end
let(:issue_error) { invalid_issue.errors.full_messages.to_sentence }
it 'returns and logs the issue error' do
expect_next_instance_of(Issues::CreateService) do |issue_service|
expect(issue_service).to receive(:execute).and_return(invalid_issue)
end
expect(service)
.to receive(:log_error)
.with(error_message(issue_error))
expect(execute).to be_error
expect(execute.message).to eq(issue_error)
end
end
shared_examples 'GFM template' do
context 'plain content' do
let(:template_content) { 'some content' }
it 'creates an issue appending issue template' do
expect(execute).to be_success
expect(issue.description).to include(alert_presenter.issue_summary_markdown)
expect(separator_count(issue.description)).to eq(1)
expect(issue.description).to include(template_content)
end
end
context 'quick actions' do
let(:user) { create(:user) }
let(:plain_text) { 'some content' }
let(:template_content) do
<<~CONTENT
#{plain_text}
/due tomorrow
/assign @#{user.username}
CONTENT
end
before do
project.add_maintainer(user)
end
it 'creates an issue interpreting quick actions' do
expect(execute).to be_success
expect(issue.description).to include(plain_text)
expect(issue.due_date).to be_present
expect(issue.assignees).to eq([user])
end
end
end
context 'with gitlab_incident_markdown' do
let(:alert_annotations) do
{ title: alert_title, gitlab_incident_markdown: template_content }
end
it_behaves_like 'GFM template'
end
context 'with issue_template_content' do
before do
create_issue_template('bug', template_content)
setting.update!(issue_template_key: 'bug')
end
it_behaves_like 'GFM template'
context 'and gitlab_incident_markdown' do
let(:template_content) { 'plain text'}
let(:alt_template) { 'alternate text' }
let(:alert_annotations) do
{ title: alert_title, gitlab_incident_markdown: alt_template }
end
it 'includes both templates' do
expect(execute).to be_success
expect(issue.description).to include(alert_presenter.issue_summary_markdown)
expect(issue.description).to include(template_content)
expect(issue.description).to include(alt_template)
expect(separator_count(issue.description)).to eq(2)
end
end
private
def create_issue_template(name, content)
project.repository.create_file(
project.creator,
".gitlab/issue_templates/#{name}.md",
content,
message: 'message',
branch_name: 'master'
)
end
end
context 'with gitlab alert' do
let(:gitlab_alert) { create(:prometheus_alert, project: project) }
before do
alert_payload['labels'] = {
'gitlab_alert_id' => gitlab_alert.prometheus_metric_id.to_s
}
end
it 'creates an issue' do
query_title = "#{gitlab_alert.title} #{gitlab_alert.computed_operator} #{gitlab_alert.threshold}"
expect(execute).to be_success
expect(issue.author).to eq(user)
expect(issue.title).to eq(alert_presenter.full_title)
expect(issue.title).to include(gitlab_alert.environment.name)
expect(issue.title).to include(query_title)
expect(issue.title).to include('for 5 minutes')
expect(issue.description).to include(alert_presenter.issue_summary_markdown.strip)
expect(separator_count(issue.description)).to eq(0)
end
end
describe 'with invalid alert payload' do
shared_examples 'invalid alert' do
it 'does not create an issue' do
expect(service)
.to receive(:log_error)
.with(error_message('invalid alert'))
expect(execute).to be_error
expect(execute.message).to eq('invalid alert')
end
end
context 'without title' do
let(:alert_annotations) { {} }
it_behaves_like 'invalid alert'
end
context 'without startsAt' do
let(:alert_starts_at) { nil }
it_behaves_like 'invalid alert'
end
end
end
context 'when create_issue disabled' do
before do
setting.update!(create_issue: false)
end
it 'returns an error' do
expect(service)
.to receive(:log_error)
.with(error_message('setting disabled'))
expect(execute).to be_error
expect(execute.message).to eq('setting disabled')
end
end
private
def build_alert_payload(annotations: {}, starts_at: Time.current)
{
'annotations' => annotations.stringify_keys
}.tap do |payload|
payload['startsAt'] = starts_at.rfc3339 if starts_at
end
end
def error_message(message)
%{Cannot create incident issue for "#{project.full_name}": #{message}}
end
def separator_count(text)
summary_separator = "\n\n---\n\n"
text.scan(summary_separator).size
end
end
......@@ -18,15 +18,15 @@ RSpec.describe IncidentManagement::ProcessAlertWorker do
before do
allow(Gitlab::AppLogger).to receive(:warn).and_call_original
allow(IncidentManagement::CreateIssueService)
.to receive(:new).with(alert.project, alert)
allow(AlertManagement::CreateAlertIssueService)
.to receive(:new).with(alert, User.alert_bot)
.and_call_original
end
shared_examples 'creates issue successfully' do
it 'creates an issue' do
expect(IncidentManagement::CreateIssueService)
.to receive(:new).with(alert.project, alert)
expect(AlertManagement::CreateAlertIssueService)
.to receive(:new).with(alert, User.alert_bot)
expect { subject }.to change { Issue.count }.by(1)
end
......@@ -58,10 +58,10 @@ RSpec.describe IncidentManagement::ProcessAlertWorker do
subject
expect(Gitlab::AppLogger).to have_received(:warn).with(
message: 'Cannot link an Issue with Alert',
message: 'Cannot process an Incident',
issue_id: created_issue.id,
alert_id: alert.id,
alert_errors: { hosts: ['hosts array is over 255 chars'] }
errors: 'Hosts hosts array is over 255 chars'
)
end
end
......@@ -80,7 +80,7 @@ RSpec.describe IncidentManagement::ProcessAlertWorker do
subject { described_class.new.perform(nil, nil, invalid_alert_id) }
it 'does not create issues' do
expect(IncidentManagement::CreateIssueService).not_to receive(:new)
expect(AlertManagement::CreateAlertIssueService).not_to receive(:new)
expect { subject }.not_to change { Issue.count }
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