Commit 15ce5ff8 authored by Sarah Yasonik's avatar Sarah Yasonik Committed by Kerri Miller

Swap alert emails to trigger at the same point

Updates alert emails to fire after an alert has been
saved. Takes a step closer to unifying prometheus
and generic alert notification processes.
parent 26087e16
...@@ -56,12 +56,9 @@ module Emails ...@@ -56,12 +56,9 @@ module Emails
subject: @message.subject) subject: @message.subject)
end end
def prometheus_alert_fired_email(project_id, user_id, alert_attributes) def prometheus_alert_fired_email(project, user, alert)
@project = ::Project.find(project_id) @project = project
user = ::User.find(user_id) @alert = alert.present
@alert = AlertManagement::Alert.new(alert_attributes.with_indifferent_access).present
return unless @alert.parsed_payload.has_required_attributes?
subject_text = "Alert: #{@alert.email_title}" subject_text = "Alert: #{@alert.email_title}"
mail(to: user.notification_email_for(@project.group), subject: subject(subject_text)) mail(to: user.notification_email_for(@project.group), subject: subject(subject_text))
......
...@@ -9,6 +9,10 @@ module AlertManagement ...@@ -9,6 +9,10 @@ module AlertManagement
return bad_request unless incoming_payload.has_required_attributes? return bad_request unless incoming_payload.has_required_attributes?
process_alert_management_alert process_alert_management_alert
return bad_request unless alert.persisted?
process_incident_issues if process_issues?
send_alert_email if send_email?
ServiceResponse.success ServiceResponse.success
end end
...@@ -30,8 +34,6 @@ module AlertManagement ...@@ -30,8 +34,6 @@ module AlertManagement
else else
create_alert_management_alert create_alert_management_alert
end end
process_incident_issues if process_issues?
end end
def reset_alert_management_alert_status def reset_alert_management_alert_status
...@@ -85,12 +87,17 @@ module AlertManagement ...@@ -85,12 +87,17 @@ module AlertManagement
end end
def process_incident_issues def process_incident_issues
return unless alert.persisted? return if alert.issue || alert.resolved?
return if alert.issue
IncidentManagement::ProcessAlertWorker.perform_async(nil, nil, alert.id) IncidentManagement::ProcessAlertWorker.perform_async(nil, nil, alert.id)
end end
def send_alert_email
notification_service
.async
.prometheus_alerts_fired(project, [alert])
end
def logger def logger
@logger ||= Gitlab::AppLogger @logger ||= Gitlab::AppLogger
end end
......
...@@ -601,7 +601,7 @@ class NotificationService ...@@ -601,7 +601,7 @@ class NotificationService
return if project.emails_disabled? return if project.emails_disabled?
owners_and_maintainers_without_invites(project).to_a.product(alerts).each do |recipient, alert| owners_and_maintainers_without_invites(project).to_a.product(alerts).each do |recipient, alert|
mailer.prometheus_alert_fired_email(project.id, recipient.user.id, alert).deliver_later mailer.prometheus_alert_fired_email(project, recipient.user, alert).deliver_later
end end
end end
......
...@@ -73,7 +73,7 @@ module Projects ...@@ -73,7 +73,7 @@ module Projects
end end
def process_incident_issues def process_incident_issues
return if alert.issue return if alert.issue || alert.resolved?
::IncidentManagement::ProcessAlertWorker.perform_async(nil, nil, alert.id) ::IncidentManagement::ProcessAlertWorker.perform_async(nil, nil, alert.id)
end end
...@@ -81,7 +81,7 @@ module Projects ...@@ -81,7 +81,7 @@ module Projects
def send_alert_email def send_alert_email
notification_service notification_service
.async .async
.prometheus_alerts_fired(project, [alert.attributes]) .prometheus_alerts_fired(project, [alert])
end end
def alert def alert
......
...@@ -23,7 +23,6 @@ module Projects ...@@ -23,7 +23,6 @@ module Projects
return unauthorized unless valid_alert_manager_token?(token) return unauthorized unless valid_alert_manager_token?(token)
process_prometheus_alerts process_prometheus_alerts
send_alert_email if send_email?
ServiceResponse.success ServiceResponse.success
end end
...@@ -120,14 +119,6 @@ module Projects ...@@ -120,14 +119,6 @@ module Projects
ActiveSupport::SecurityUtils.secure_compare(expected, actual) ActiveSupport::SecurityUtils.secure_compare(expected, actual)
end end
def send_alert_email
return unless firings.any?
notification_service
.async
.prometheus_alerts_fired(project, alerts_attributes)
end
def process_prometheus_alerts def process_prometheus_alerts
alerts.each do |alert| alerts.each do |alert|
AlertManagement::ProcessPrometheusAlertService AlertManagement::ProcessPrometheusAlertService
...@@ -136,18 +127,6 @@ module Projects ...@@ -136,18 +127,6 @@ module Projects
end end
end end
def alerts_attributes
firings.map do |payload|
alert_params = Gitlab::AlertManagement::Payload.parse(
project,
payload,
monitoring_tool: Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus]
).alert_params
AlertManagement::Alert.new(alert_params).attributes
end
end
def bad_request def bad_request
ServiceResponse.error(message: 'Bad Request', http_status: :bad_request) ServiceResponse.error(message: 'Bad Request', http_status: :bad_request)
end end
......
- body = @alert.resolved? ? _('An alert has been resolved in %{project_path}.') : _('An alert has been triggered in %{project_path}.')
%p
= body % { project_path: @alert.project.full_path }
%p %p
= _('An alert has been triggered in %{project_path}.') % { project_path: @alert.project.full_path } = link_to(_('View alert details.'), @alert.details_url)
- if description = @alert.description - if description = @alert.description
%p %p
......
<%= _('An alert has been triggered in %{project_path}.') % { project_path: @alert.project.full_path } %>. <% body = @alert.resolved? ? _('An alert has been resolved in %{project_path}.') : _('An alert has been triggered in %{project_path}.') %>
<%= body % { project_path: @alert.project.full_path } %>
<%= _('View alert details at') %> <%= @alert.details_url %>
<% if description = @alert.description %> <% if description = @alert.description %>
<%= _('Description:') %> <%= description %> <%= _('Description:') %> <%= description %>
......
---
title: Improve messaging for emails from alerts
merge_request: 43054
author:
type: changed
...@@ -2813,6 +2813,9 @@ msgstr "" ...@@ -2813,6 +2813,9 @@ msgstr ""
msgid "An administrator changed the password for your GitLab account on %{link_to}." msgid "An administrator changed the password for your GitLab account on %{link_to}."
msgstr "" msgstr ""
msgid "An alert has been resolved in %{project_path}."
msgstr ""
msgid "An alert has been triggered in %{project_path}." msgid "An alert has been triggered in %{project_path}."
msgstr "" msgstr ""
...@@ -29313,6 +29316,12 @@ msgstr "" ...@@ -29313,6 +29316,12 @@ msgstr ""
msgid "View Documentation" msgid "View Documentation"
msgstr "" msgstr ""
msgid "View alert details at"
msgstr ""
msgid "View alert details."
msgstr ""
msgid "View all issues" msgid "View all issues"
msgstr "" msgstr ""
......
...@@ -32,19 +32,13 @@ RSpec.describe Emails::Projects do ...@@ -32,19 +32,13 @@ RSpec.describe Emails::Projects do
describe '#prometheus_alert_fired_email' do describe '#prometheus_alert_fired_email' do
let(:default_title) { Gitlab::AlertManagement::Payload::Generic::DEFAULT_TITLE } let(:default_title) { Gitlab::AlertManagement::Payload::Generic::DEFAULT_TITLE }
let(:payload) { { 'startsAt' => Time.now.rfc3339 } } let(:payload) { { 'startsAt' => Time.now.rfc3339 } }
let(:alert_attributes) { build(:alert_management_alert, :from_payload, payload: payload, project: project).attributes } let(:alert) { create(:alert_management_alert, :from_payload, payload: payload, project: project) }
subject do subject do
Notify.prometheus_alert_fired_email(project.id, user.id, alert_attributes) Notify.prometheus_alert_fired_email(project, user, alert)
end end
context 'missing required attributes' do context 'with empty payload' do
let(:alert_attributes) { build(:alert_management_alert, :prometheus, :from_payload, payload: payload, project: project).attributes }
it_behaves_like 'no email'
end
context 'with minimum required attributes' do
let(:payload) { {} } let(:payload) { {} }
it_behaves_like 'an email sent from GitLab' it_behaves_like 'an email sent from GitLab'
...@@ -58,6 +52,7 @@ RSpec.describe Emails::Projects do ...@@ -58,6 +52,7 @@ RSpec.describe Emails::Projects do
it 'has expected content' do it 'has expected content' do
is_expected.to have_body_text('An alert has been triggered') is_expected.to have_body_text('An alert has been triggered')
is_expected.to have_body_text(project.full_path) is_expected.to have_body_text(project.full_path)
is_expected.to have_body_text(alert.details_url)
is_expected.not_to have_body_text('Description:') is_expected.not_to have_body_text('Description:')
is_expected.not_to have_body_text('Environment:') is_expected.not_to have_body_text('Environment:')
is_expected.not_to have_body_text('Metric:') is_expected.not_to have_body_text('Metric:')
...@@ -78,6 +73,7 @@ RSpec.describe Emails::Projects do ...@@ -78,6 +73,7 @@ RSpec.describe Emails::Projects do
it 'has expected content' do it 'has expected content' do
is_expected.to have_body_text('An alert has been triggered') is_expected.to have_body_text('An alert has been triggered')
is_expected.to have_body_text(project.full_path) is_expected.to have_body_text(project.full_path)
is_expected.to have_body_text(alert.details_url)
is_expected.to have_body_text('Description:') is_expected.to have_body_text('Description:')
is_expected.to have_body_text('alert description') is_expected.to have_body_text('alert description')
is_expected.not_to have_body_text('Environment:') is_expected.not_to have_body_text('Environment:')
...@@ -101,6 +97,7 @@ RSpec.describe Emails::Projects do ...@@ -101,6 +97,7 @@ RSpec.describe Emails::Projects do
it 'has expected content' do it 'has expected content' do
is_expected.to have_body_text('An alert has been triggered') is_expected.to have_body_text('An alert has been triggered')
is_expected.to have_body_text(project.full_path) is_expected.to have_body_text(project.full_path)
is_expected.to have_body_text(alert.details_url)
is_expected.to have_body_text('Environment:') is_expected.to have_body_text('Environment:')
is_expected.to have_body_text(environment.name) is_expected.to have_body_text(environment.name)
is_expected.not_to have_body_text('Description:') is_expected.not_to have_body_text('Description:')
...@@ -112,7 +109,7 @@ RSpec.describe Emails::Projects do ...@@ -112,7 +109,7 @@ RSpec.describe Emails::Projects do
let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) } let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) }
let_it_be(:environment) { prometheus_alert.environment } let_it_be(:environment) { prometheus_alert.environment }
let(:alert_attributes) { build(:alert_management_alert, :prometheus, :from_payload, payload: payload, project: project).attributes } let(:alert) { create(:alert_management_alert, :prometheus, :from_payload, payload: payload, project: project) }
let(:title) { "#{prometheus_alert.title} #{prometheus_alert.computed_operator} #{prometheus_alert.threshold}" } let(:title) { "#{prometheus_alert.title} #{prometheus_alert.computed_operator} #{prometheus_alert.threshold}" }
let(:metrics_url) { metrics_project_environment_url(project, environment) } let(:metrics_url) { metrics_project_environment_url(project, environment) }
...@@ -135,6 +132,7 @@ RSpec.describe Emails::Projects do ...@@ -135,6 +132,7 @@ RSpec.describe Emails::Projects do
it 'has expected content' do it 'has expected content' do
is_expected.to have_body_text('An alert has been triggered') is_expected.to have_body_text('An alert has been triggered')
is_expected.to have_body_text(project.full_path) is_expected.to have_body_text(project.full_path)
is_expected.to have_body_text(alert.details_url)
is_expected.to have_body_text('Environment:') is_expected.to have_body_text('Environment:')
is_expected.to have_body_text(environment.name) is_expected.to have_body_text(environment.name)
is_expected.to have_body_text('Metric:') is_expected.to have_body_text('Metric:')
...@@ -143,5 +141,23 @@ RSpec.describe Emails::Projects do ...@@ -143,5 +141,23 @@ RSpec.describe Emails::Projects do
is_expected.not_to have_body_text('Description:') is_expected.not_to have_body_text('Description:')
end end
end end
context 'resolved' do
let_it_be(:alert) { create(:alert_management_alert, :resolved, project: project) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'has expected subject' do
is_expected.to have_subject("#{project.name} | Alert: #{alert.title}")
end
it 'has expected content' do
is_expected.to have_body_text('An alert has been resolved')
is_expected.to have_body_text(project.full_path)
is_expected.to have_body_text(alert.details_url)
end
end
end end
end end
...@@ -11,9 +11,16 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do ...@@ -11,9 +11,16 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
describe '#execute' do describe '#execute' do
let(:service) { described_class.new(project, nil, payload) } let(:service) { described_class.new(project, nil, payload) }
let(:incident_management_setting) { double(auto_close_incident?: auto_close_incident, create_issue?: create_issue) }
let(:auto_close_incident) { true } let(:auto_close_incident) { true }
let(:create_issue) { true } let(:create_issue) { true }
let(:send_email) { true }
let(:incident_management_setting) do
double(
auto_close_incident?: auto_close_incident,
create_issue?: create_issue,
send_email?: send_email
)
end
before do before do
allow(service) allow(service)
...@@ -55,6 +62,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do ...@@ -55,6 +62,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
it_behaves_like 'adds an alert management alert event' it_behaves_like 'adds an alert management alert event'
it_behaves_like 'processes incident issues' it_behaves_like 'processes incident issues'
it_behaves_like 'Alert Notification Service sends notification email'
context 'existing alert is resolved' do context 'existing alert is resolved' do
let!(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: fingerprint) } let!(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: fingerprint) }
...@@ -92,28 +100,48 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do ...@@ -92,28 +100,48 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
end end
end end
context 'when auto-alert creation is disabled' do context 'when auto-creation of issues is disabled' do
let(:create_issue) { false } let(:create_issue) { false }
it_behaves_like 'does not process incident issues' it_behaves_like 'does not process incident issues'
end end
context 'when emails are disabled' do
let(:send_email) { false }
it 'does not send notification' do
expect(NotificationService).not_to receive(:new)
expect(subject).to be_success
end
end
end end
context 'when alert does not exist' do context 'when alert does not exist' do
context 'when alert can be created' do context 'when alert can be created' do
it_behaves_like 'creates an alert management alert' it_behaves_like 'creates an alert management alert'
it_behaves_like 'Alert Notification Service sends notification email'
it_behaves_like 'processes incident issues'
it 'creates a system note corresponding to alert creation' do it 'creates a system note corresponding to alert creation' do
expect { subject }.to change(Note, :count).by(1) expect { subject }.to change(Note, :count).by(1)
end end
it_behaves_like 'processes incident issues'
context 'when auto-alert creation is disabled' do context 'when auto-alert creation is disabled' do
let(:create_issue) { false } let(:create_issue) { false }
it_behaves_like 'does not process incident issues' it_behaves_like 'does not process incident issues'
end end
context 'when emails are disabled' do
let(:send_email) { false }
it 'does not send notification' do
expect(NotificationService).not_to receive(:new)
expect(subject).to be_success
end
end
end end
context 'when alert cannot be created' do context 'when alert cannot be created' do
...@@ -125,6 +153,9 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do ...@@ -125,6 +153,9 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
allow(service).to receive_message_chain(:alert, :errors).and_return(errors) allow(service).to receive_message_chain(:alert, :errors).and_return(errors)
end end
it_behaves_like 'Alert Notification Service sends no notifications', http_status: :bad_request
it_behaves_like 'does not process incident issues due to error', http_status: :bad_request
it 'writes a warning to the log' do it 'writes a warning to the log' do
expect(Gitlab::AppLogger).to receive(:warn).with( expect(Gitlab::AppLogger).to receive(:warn).with(
message: 'Unable to create AlertManagement::Alert', message: 'Unable to create AlertManagement::Alert',
...@@ -134,8 +165,6 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do ...@@ -134,8 +165,6 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
execute execute
end end
it_behaves_like 'does not process incident issues'
end end
it { is_expected.to be_success } it { is_expected.to be_success }
...@@ -148,6 +177,9 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do ...@@ -148,6 +177,9 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
context 'when auto_resolve_incident set to true' do context 'when auto_resolve_incident set to true' do
context 'when status can be changed' do context 'when status can be changed' do
it_behaves_like 'Alert Notification Service sends notification email'
it_behaves_like 'does not process incident issues'
it 'resolves an existing alert' do it 'resolves an existing alert' do
expect { execute }.to change { alert.reload.resolved? }.to(true) expect { execute }.to change { alert.reload.resolved? }.to(true)
end end
...@@ -185,6 +217,8 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do ...@@ -185,6 +217,8 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
execute execute
end end
it_behaves_like 'Alert Notification Service sends notification email'
end end
it { is_expected.to be_success } it { is_expected.to be_success }
...@@ -197,6 +231,16 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do ...@@ -197,6 +231,16 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
expect { execute }.not_to change { alert.reload.resolved? } expect { execute }.not_to change { alert.reload.resolved? }
end end
end end
context 'when emails are disabled' do
let(:send_email) { false }
it 'does not send notification' do
expect(NotificationService).not_to receive(:new)
expect(subject).to be_success
end
end
end end
context 'environment given' do context 'environment given' do
......
...@@ -3100,26 +3100,26 @@ RSpec.describe NotificationService, :mailer do ...@@ -3100,26 +3100,26 @@ RSpec.describe NotificationService, :mailer do
end end
describe '#prometheus_alerts_fired' do describe '#prometheus_alerts_fired' do
let!(:project) { create(:project) } let_it_be(:project) { create(:project) }
let!(:master) { create(:user) } let_it_be(:master) { create(:user) }
let!(:developer) { create(:user) } let_it_be(:developer) { create(:user) }
let(:alert_attributes) { build(:alert_management_alert, project: project).attributes } let_it_be(:alert) { create(:alert_management_alert, project: project) }
before do before do
project.add_maintainer(master) project.add_maintainer(master)
end end
it 'sends the email to owners and masters' do it 'sends the email to owners and masters' do
expect(Notify).to receive(:prometheus_alert_fired_email).with(project.id, master.id, alert_attributes).and_call_original expect(Notify).to receive(:prometheus_alert_fired_email).with(project, master, alert).and_call_original
expect(Notify).to receive(:prometheus_alert_fired_email).with(project.id, project.owner.id, alert_attributes).and_call_original expect(Notify).to receive(:prometheus_alert_fired_email).with(project, project.owner, alert).and_call_original
expect(Notify).not_to receive(:prometheus_alert_fired_email).with(project.id, developer.id, alert_attributes) expect(Notify).not_to receive(:prometheus_alert_fired_email).with(project, developer, alert)
subject.prometheus_alerts_fired(project, [alert_attributes]) subject.prometheus_alerts_fired(project, [alert])
end end
it_behaves_like 'project emails are disabled' do it_behaves_like 'project emails are disabled' do
let(:notification_target) { project } let(:notification_target) { project }
let(:notification_trigger) { subject.prometheus_alerts_fired(project, [alert_attributes]) } let(:notification_trigger) { subject.prometheus_alerts_fired(project, [alert]) }
around do |example| around do |example|
perform_enqueued_jobs { example.run } perform_enqueued_jobs { example.run }
......
...@@ -129,6 +129,12 @@ RSpec.describe Projects::Alerting::NotifyService do ...@@ -129,6 +129,12 @@ RSpec.describe Projects::Alerting::NotifyService do
it { expect { subject }.to change { issue.reload.state }.from('opened').to('closed') } it { expect { subject }.to change { issue.reload.state }.from('opened').to('closed') }
it { expect { subject }.to change(ResourceStateEvent, :count).by(1) } it { expect { subject }.to change(ResourceStateEvent, :count).by(1) }
end end
context 'with issue enabled' do
let(:issue_enabled) { true }
it_behaves_like 'does not process incident issues'
end
end 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