Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0720781256
commit
c8003cdfe1
16 changed files with 324 additions and 7 deletions
|
@ -150,8 +150,19 @@ module AlertManagement
|
|||
''
|
||||
end
|
||||
|
||||
def execute_services
|
||||
return unless Feature.enabled?(:alert_slack_event, project)
|
||||
return unless project.has_active_services?(:alert_hooks)
|
||||
|
||||
project.execute_services(hook_data, :alert_hooks)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hook_data
|
||||
Gitlab::DataBuilder::Alert.build(self)
|
||||
end
|
||||
|
||||
def hosts_length
|
||||
return unless hosts
|
||||
|
||||
|
|
74
app/models/project_services/chat_message/alert_message.rb
Normal file
74
app/models/project_services/chat_message/alert_message.rb
Normal file
|
@ -0,0 +1,74 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ChatMessage
|
||||
class AlertMessage < BaseMessage
|
||||
attr_reader :title
|
||||
attr_reader :alert_url
|
||||
attr_reader :severity
|
||||
attr_reader :events
|
||||
attr_reader :status
|
||||
attr_reader :started_at
|
||||
|
||||
def initialize(params)
|
||||
@project_name = params[:project_name] || params.dig(:project, :path_with_namespace)
|
||||
@project_url = params.dig(:project, :web_url) || params[:project_url]
|
||||
@title = params.dig(:object_attributes, :title)
|
||||
@alert_url = params.dig(:object_attributes, :url)
|
||||
@severity = params.dig(:object_attributes, :severity)
|
||||
@events = params.dig(:object_attributes, :events)
|
||||
@status = params.dig(:object_attributes, :status)
|
||||
@started_at = params.dig(:object_attributes, :started_at)
|
||||
end
|
||||
|
||||
def attachments
|
||||
[{
|
||||
title: title,
|
||||
title_link: alert_url,
|
||||
color: attachment_color,
|
||||
fields: attachment_fields
|
||||
}]
|
||||
end
|
||||
|
||||
def message
|
||||
"Alert firing in #{project_name}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def attachment_color
|
||||
"#C95823"
|
||||
end
|
||||
|
||||
def attachment_fields
|
||||
[
|
||||
{
|
||||
title: "Severity",
|
||||
value: severity.to_s.humanize,
|
||||
short: true
|
||||
},
|
||||
{
|
||||
title: "Events",
|
||||
value: events,
|
||||
short: true
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
value: status.to_s.humanize,
|
||||
short: true
|
||||
},
|
||||
{
|
||||
title: "Start time",
|
||||
value: format_time(started_at),
|
||||
short: true
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
# This formats time into the following format
|
||||
# April 23rd, 2020 1:06AM UTC
|
||||
def format_time(time)
|
||||
time = Time.zone.parse(time.to_s)
|
||||
time.strftime("%B #{time.day.ordinalize}, %Y%l:%M%p %Z")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SlackService < ChatNotificationService
|
||||
prop_accessor EVENT_CHANNEL['alert']
|
||||
|
||||
def title
|
||||
'Slack notifications'
|
||||
end
|
||||
|
@ -21,13 +23,25 @@ class SlackService < ChatNotificationService
|
|||
'https://hooks.slack.com/services/…'
|
||||
end
|
||||
|
||||
def supported_events
|
||||
additional = []
|
||||
additional << 'alert' if Feature.enabled?(:alert_slack_event, project)
|
||||
|
||||
super + additional
|
||||
end
|
||||
|
||||
def get_message(object_kind, data)
|
||||
return ChatMessage::AlertMessage.new(data) if object_kind == 'alert'
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
module Notifier
|
||||
private
|
||||
|
||||
def notify(message, opts)
|
||||
# See https://gitlab.com/gitlab-org/slack-notifier/#custom-http-client
|
||||
notifier = Slack::Messenger.new(webhook, opts.merge(http_client: HTTPClient))
|
||||
|
||||
notifier.ping(
|
||||
message.pretext,
|
||||
attachments: message.attachments,
|
||||
|
|
|
@ -22,6 +22,7 @@ class Service < ApplicationRecord
|
|||
serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
||||
default_value_for :active, false
|
||||
default_value_for :alert_events, true
|
||||
default_value_for :push_events, true
|
||||
default_value_for :issues_events, true
|
||||
default_value_for :confidential_issues_events, true
|
||||
|
@ -72,6 +73,7 @@ class Service < ApplicationRecord
|
|||
scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
|
||||
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
|
||||
scope :deployment_hooks, -> { where(deployment_events: true, active: true) }
|
||||
scope :alert_hooks, -> { where(alert_events: true, active: true) }
|
||||
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
|
||||
scope :deployment, -> { where(category: 'deployment') }
|
||||
|
||||
|
@ -172,7 +174,7 @@ class Service < ApplicationRecord
|
|||
end
|
||||
|
||||
def configurable_events
|
||||
events = self.class.supported_events
|
||||
events = supported_events
|
||||
|
||||
# No need to disable individual triggers when there is only one
|
||||
if events.count == 1
|
||||
|
@ -403,6 +405,8 @@ class Service < ApplicationRecord
|
|||
"Event will be triggered when a commit is created/updated"
|
||||
when "deployment"
|
||||
"Event will be triggered when a deployment finishes"
|
||||
when "alert"
|
||||
"Event will be triggered when a new, unique alert is recorded"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -48,7 +48,10 @@ module AlertManagement
|
|||
|
||||
def create_alert_management_alert
|
||||
am_alert = AlertManagement::Alert.new(am_alert_params.merge(ended_at: nil))
|
||||
return if am_alert.save
|
||||
if am_alert.save
|
||||
am_alert.execute_services
|
||||
return
|
||||
end
|
||||
|
||||
logger.warn(
|
||||
message: 'Unable to create AlertManagement::Alert',
|
||||
|
|
|
@ -32,15 +32,24 @@ module Projects
|
|||
end
|
||||
|
||||
def process_alert
|
||||
if alert = find_alert_by_fingerprint(am_alert_params[:fingerprint])
|
||||
alert.register_new_event!
|
||||
existing_alert = find_alert_by_fingerprint(am_alert_params[:fingerprint])
|
||||
|
||||
if existing_alert
|
||||
process_existing_alert(existing_alert)
|
||||
else
|
||||
create_alert
|
||||
end
|
||||
end
|
||||
|
||||
def process_existing_alert(alert)
|
||||
alert.register_new_event!
|
||||
end
|
||||
|
||||
def create_alert
|
||||
AlertManagement::Alert.create(am_alert_params)
|
||||
alert = AlertManagement::Alert.create(am_alert_params)
|
||||
alert.execute_services if alert.persisted?
|
||||
|
||||
alert
|
||||
end
|
||||
|
||||
def find_alert_by_fingerprint(fingerprint)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add column for alert slack notifications
|
||||
merge_request: 33017
|
||||
author:
|
||||
type: added
|
19
db/migrate/20200526013844_add_alert_events_to_services.rb
Normal file
19
db/migrate/20200526013844_add_alert_events_to_services.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddAlertEventsToServices < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
add_column :services, :alert_events, :boolean
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_column :services, :alert_events
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6147,7 +6147,8 @@ CREATE TABLE public.services (
|
|||
template boolean DEFAULT false,
|
||||
instance boolean DEFAULT false NOT NULL,
|
||||
comment_detail smallint,
|
||||
inherit_from_id bigint
|
||||
inherit_from_id bigint,
|
||||
alert_events boolean
|
||||
);
|
||||
|
||||
CREATE SEQUENCE public.services_id_seq
|
||||
|
@ -13778,6 +13779,7 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200525114553
|
||||
20200525121014
|
||||
20200526000407
|
||||
20200526013844
|
||||
20200526120714
|
||||
20200526153844
|
||||
20200526164946
|
||||
|
|
27
lib/gitlab/data_builder/alert.rb
Normal file
27
lib/gitlab/data_builder/alert.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module DataBuilder
|
||||
module Alert
|
||||
extend self
|
||||
|
||||
def build(alert)
|
||||
{
|
||||
object_kind: 'alert',
|
||||
object_attributes: hook_attrs(alert)
|
||||
}
|
||||
end
|
||||
|
||||
def hook_attrs(alert)
|
||||
{
|
||||
title: alert.title,
|
||||
url: Gitlab::Routing.url_helpers.details_project_alert_management_url(alert.project, alert.iid),
|
||||
severity: alert.severity,
|
||||
events: alert.events,
|
||||
status: alert.status_name,
|
||||
started_at: alert.started_at
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
26
spec/lib/gitlab/data_builder/alert_spec.rb
Normal file
26
spec/lib/gitlab/data_builder/alert_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::DataBuilder::Alert do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:alert) { create(:alert_management_alert, project: project) }
|
||||
|
||||
describe '.build' do
|
||||
let_it_be(:data) { described_class.build(alert) }
|
||||
|
||||
it { expect(data).to be_a(Hash) }
|
||||
it { expect(data[:object_kind]).to eq('alert') }
|
||||
|
||||
it 'contains the correct object attributes', :aggregate_failures do
|
||||
object_attributes = data[:object_attributes]
|
||||
|
||||
expect(object_attributes[:title]).to eq(alert.title)
|
||||
expect(object_attributes[:url]).to eq(Gitlab::Routing.url_helpers.details_project_alert_management_url(project, alert.iid))
|
||||
expect(object_attributes[:severity]).to eq(alert.severity)
|
||||
expect(object_attributes[:events]).to eq(alert.events)
|
||||
expect(object_attributes[:status]).to eq(alert.status_name)
|
||||
expect(object_attributes[:started_at]).to eq(alert.started_at)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -472,6 +472,7 @@ Service:
|
|||
- properties
|
||||
- template
|
||||
- instance
|
||||
- alert_events
|
||||
- push_events
|
||||
- issues_events
|
||||
- commit_events
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ChatMessage::AlertMessage do
|
||||
subject { described_class.new(args) }
|
||||
|
||||
let(:alert) { create(:alert_management_alert) }
|
||||
|
||||
let(:args) do
|
||||
{
|
||||
project_name: 'project_name',
|
||||
project_url: 'http://example.com'
|
||||
}.merge(Gitlab::DataBuilder::Alert.build(alert))
|
||||
end
|
||||
|
||||
describe '#message' do
|
||||
it 'returns the correct message' do
|
||||
expect(subject.message).to eq("Alert firing in #{args[:project_name]}")
|
||||
end
|
||||
end
|
||||
|
||||
describe '#attachments' do
|
||||
it 'returns an array of one' do
|
||||
expect(subject.attachments).to be_a(Array)
|
||||
expect(subject.attachments.size).to eq(1)
|
||||
end
|
||||
|
||||
it 'contains the correct attributes' do
|
||||
attachments_item = subject.attachments.first
|
||||
expect(attachments_item).to have_key(:title)
|
||||
expect(attachments_item).to have_key(:title_link)
|
||||
expect(attachments_item).to have_key(:color)
|
||||
expect(attachments_item).to have_key(:fields)
|
||||
end
|
||||
|
||||
it 'returns the correct color' do
|
||||
expect(subject.attachments.first[:color]).to eq("#C95823")
|
||||
end
|
||||
|
||||
it 'returns the correct attachment fields' do
|
||||
attachments_item = subject.attachments.first
|
||||
fields = attachments_item[:fields].map { |h| h[:title] }
|
||||
|
||||
expect(fields).to match_array(['Severity', 'Events', 'Status', 'Start time'])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -114,6 +114,20 @@ describe Service do
|
|||
expect(described_class.confidential_note_hooks.count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
describe '.alert_hooks' do
|
||||
it 'includes services where alert_events is true' do
|
||||
create(:service, active: true, alert_events: true)
|
||||
|
||||
expect(described_class.alert_hooks.count).to eq 1
|
||||
end
|
||||
|
||||
it 'excludes services where alert_events is false' do
|
||||
create(:service, active: true, alert_events: false)
|
||||
|
||||
expect(described_class.alert_hooks.count).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Test Button" do
|
||||
|
|
|
@ -5,6 +5,10 @@ require 'spec_helper'
|
|||
RSpec.describe AlertManagement::ProcessPrometheusAlertService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
before do
|
||||
allow(ProjectServiceWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
subject(:execute) { described_class.new(project, nil, payload).execute }
|
||||
|
||||
|
@ -47,6 +51,12 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
|
|||
end
|
||||
end
|
||||
|
||||
it 'does not executes the alert service hooks' do
|
||||
expect(alert).not_to receive(:execute_services)
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'when status change did not succeed' do
|
||||
before do
|
||||
allow(AlertManagement::Alert).to receive(:for_fingerprint).and_return([alert])
|
||||
|
@ -72,6 +82,26 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
|
|||
it 'creates a new alert' do
|
||||
expect { execute }.to change { AlertManagement::Alert.where(project: project).count }.by(1)
|
||||
end
|
||||
|
||||
it 'executes the alert service hooks' do
|
||||
slack_service = create(:service, type: 'SlackService', project: project, alert_events: true, active: true)
|
||||
|
||||
subject
|
||||
|
||||
expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash))
|
||||
end
|
||||
|
||||
context 'feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(alert_slack_event: false)
|
||||
end
|
||||
|
||||
it 'does not execute the alert service hooks' do
|
||||
subject
|
||||
|
||||
expect(ProjectServiceWorker).not_to have_received(:perform_async)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when alert cannot be created' do
|
||||
|
|
|
@ -8,12 +8,17 @@ describe Projects::Alerting::NotifyService do
|
|||
before do
|
||||
# We use `let_it_be(:project)` so we make sure to clear caches
|
||||
project.clear_memoization(:licensed_feature_available)
|
||||
allow(ProjectServiceWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
shared_examples 'processes incident issues' do |amount|
|
||||
let(:create_incident_service) { spy }
|
||||
let(:new_alert) { instance_double(AlertManagement::Alert, id: 503, persisted?: true) }
|
||||
|
||||
before do
|
||||
allow(new_alert).to receive(:execute_services)
|
||||
end
|
||||
|
||||
it 'processes issues' do
|
||||
expect(AlertManagement::Alert)
|
||||
.to receive(:create)
|
||||
|
@ -138,6 +143,25 @@ describe Projects::Alerting::NotifyService do
|
|||
)
|
||||
end
|
||||
|
||||
it 'executes the alert service hooks' do
|
||||
slack_service = create(:service, type: 'SlackService', project: project, alert_events: true, active: true)
|
||||
subject
|
||||
|
||||
expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash))
|
||||
end
|
||||
|
||||
context 'feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(alert_slack_event: false)
|
||||
end
|
||||
|
||||
it 'does not executes the alert service hooks' do
|
||||
subject
|
||||
|
||||
expect(ProjectServiceWorker).not_to have_received(:perform_async)
|
||||
end
|
||||
end
|
||||
|
||||
context 'existing alert with same fingerprint' do
|
||||
let!(:existing_alert) { create(:alert_management_alert, project: project, fingerprint: Digest::SHA1.hexdigest(fingerprint)) }
|
||||
|
||||
|
@ -148,6 +172,12 @@ describe Projects::Alerting::NotifyService do
|
|||
it 'increments the existing alert count' do
|
||||
expect { subject }.to change { existing_alert.reload.events }.from(1).to(2)
|
||||
end
|
||||
|
||||
it 'does not executes the alert service hooks' do
|
||||
subject
|
||||
|
||||
expect(ProjectServiceWorker).not_to have_received(:perform_async)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a minimal payload' do
|
||||
|
|
Loading…
Reference in a new issue