Commit 43eeda8b authored by Vitali Tatarintev's avatar Vitali Tatarintev

Apply the custom mapping to alert

Apply the custom mapping to incoming alerts
parent b488e2f5
......@@ -92,7 +92,7 @@ module AlertManagement
def incoming_payload
strong_memoize(:incoming_payload) do
Gitlab::AlertManagement::Payload.parse(project, payload.to_h)
Gitlab::AlertManagement::Payload.parse(project, payload.to_h, integration: integration)
end
end
......
......@@ -9,6 +9,7 @@ module EE
extend ::Gitlab::Utils::Override
EXCLUDED_PAYLOAD_FINGERPRINT_PARAMS = %w(start_time end_time hosts).freeze
CUSTOM_MAPPING_PATH_KEY = 'path'
private
......@@ -33,6 +34,20 @@ module EE
def generic_alert_fingerprinting_enabled?
project.feature_available?(:generic_alert_fingerprinting)
end
override :value_for_paths
def value_for_paths(paths)
custom_mapping_value_for_paths(paths) || super(paths)
end
def custom_mapping_value_for_paths(paths)
return unless ::Gitlab::AlertManagement.custom_mapping_available?(project)
return unless integration&.active?
custom_mapping_path = integration.payload_attribute_mapping.dig(*paths.first, CUSTOM_MAPPING_PATH_KEY)
payload&.dig(*custom_mapping_path) if custom_mapping_path
end
end
end
end
......
......@@ -3,10 +3,292 @@
require 'spec_helper'
RSpec.describe Gitlab::AlertManagement::Payload::Generic do
let_it_be(:project) { build_stubbed(:project) }
let_it_be(:project) { create(:project) }
let(:raw_payload) { {} }
let(:parsed_payload) { described_class.new(project: project, payload: raw_payload) }
shared_examples 'parsing alert payload fields with default paths' do
describe '#title' do
subject { parsed_payload.title }
it { is_expected.to eq('default title') }
end
describe '#description' do
subject { parsed_payload.description }
it { is_expected.to eq('default description') }
end
describe '#starts_at' do
subject { parsed_payload.starts_at }
it { is_expected.to eq(default_start_time) }
end
describe '#ends_at' do
subject { parsed_payload.ends_at }
it { is_expected.to eq(default_end_time) }
end
describe '#service' do
subject { parsed_payload.service }
it { is_expected.to eq('default service') }
end
describe '#monitoring_tool' do
subject { parsed_payload.monitoring_tool }
it { is_expected.to eq('default monitoring tool') }
end
describe '#host' do
subject { parsed_payload.hosts }
it { is_expected.to eq(['default-host']) }
end
describe '#severity' do
subject { parsed_payload.severity }
it { is_expected.to eq('low') }
end
describe '#environment_name' do
subject { parsed_payload.environment_name }
it { is_expected.to eq('default gitlab environment')}
end
describe '#gitlab_fingerprint' do
subject { parsed_payload.gitlab_fingerprint }
it { is_expected.to eq(Gitlab::AlertManagement::Fingerprint.generate('default fingerprint')) }
end
end
describe 'attributes' do
let_it_be(:default_start_time) { 10.days.ago.change(usec: 0).utc }
let_it_be(:default_end_time) { 9.days.ago.change(usec: 0).utc }
let_it_be(:mapped_start_time) { 5.days.ago.change(usec: 0).utc }
let_it_be(:mapped_end_time) { 4.days.ago.change(usec: 0).utc }
let_it_be(:raw_payload) do
{
'title' => 'default title',
'description' => 'default description',
'start_time' => default_start_time.to_s,
'end_time' => default_end_time.to_s,
'service' => 'default service',
'monitoring_tool' => 'default monitoring tool',
'hosts' => ['default-host'],
'severity' => 'low',
'gitlab_environment_name' => 'default gitlab environment',
'fingerprint' => 'default fingerprint',
'alert' => {
'name' => 'mapped title',
'desc' => 'mapped description',
'start_time' => mapped_start_time.to_s,
'end_time' => mapped_end_time.to_s,
'service' => 'mapped service',
'monitoring_tool' => 'mapped monitoring tool',
'hosts' => ['mapped-host'],
'severity' => 'high',
'env_name' => 'mapped gitlab environment',
'fingerprint' => 'mapped fingerprint'
}
}
end
context 'with multiple HTTP integrations feature available' do
before do
stub_licensed_features(multiple_alert_http_integrations: project)
end
context 'with multiple_http_integrations_custom_mapping feature flag enabled' do
let_it_be(:attribute_mapping) do
{
title: { path: %w(alert name), type: 'string' },
description: { path: %w(alert desc), type: 'string' },
start_time: { path: %w(alert start_time), type: 'datetime' },
end_time: { path: %w(alert end_time), type: 'datetime' },
service: { path: %w(alert service), type: 'string' },
monitoring_tool: { path: %w(alert monitoring_tool), type: 'string' },
hosts: { path: %w(alert hosts), type: 'string' },
severity: { path: %w(alert severity), type: 'string' },
gitlab_environment_name: { path: %w(alert env_name), type: 'string' },
fingerprint: { path: %w(alert fingerprint), type: 'string' }
}
end
let(:parsed_payload) { described_class.new(project: project, payload: raw_payload, integration: integration) }
before do
stub_feature_flags(multiple_http_integrations_custom_mapping: project)
end
context 'with defined custom mapping' do
let_it_be(:integration) do
create(:alert_management_http_integration, project: project, payload_attribute_mapping: attribute_mapping)
end
describe '#title' do
subject { parsed_payload.title }
it { is_expected.to eq('mapped title') }
end
describe '#description' do
subject { parsed_payload.description }
it { is_expected.to eq('mapped description') }
end
describe '#starts_at' do
subject { parsed_payload.starts_at }
it { is_expected.to eq(mapped_start_time) }
end
describe '#ends_at' do
subject { parsed_payload.ends_at }
it { is_expected.to eq(mapped_end_time) }
end
describe '#service' do
subject { parsed_payload.service }
it { is_expected.to eq('mapped service') }
end
describe '#monitoring_tool' do
subject { parsed_payload.monitoring_tool }
it { is_expected.to eq('mapped monitoring tool') }
end
describe '#host' do
subject { parsed_payload.hosts }
it { is_expected.to eq(['mapped-host']) }
end
describe '#severity' do
subject { parsed_payload.severity }
it { is_expected.to eq('high') }
end
describe '#environment_name' do
subject { parsed_payload.environment_name }
it { is_expected.to eq('mapped gitlab environment')}
end
describe '#gitlab_fingerprint' do
subject { parsed_payload.gitlab_fingerprint }
it { is_expected.to eq(Gitlab::AlertManagement::Fingerprint.generate('mapped fingerprint')) }
end
end
context 'with only some attributes defined in custom mapping' do
let_it_be(:attribute_mapping) do
{
title: { path: %w(alert name), type: 'string' }
}
end
let_it_be(:integration) do
create(:alert_management_http_integration, project: project, payload_attribute_mapping: attribute_mapping)
end
describe '#title' do
subject { parsed_payload.title }
it 'uses the value defined by the custom mapping' do
is_expected.to eq('mapped title')
end
end
describe '#description' do
subject { parsed_payload.description }
it 'falls back to the default value' do
is_expected.to eq('default description')
end
end
end
context 'when the payload has no default generic attributes' do
let_it_be(:raw_payload) do
{
'alert' => {
'name' => 'mapped title',
'desc' => 'mapped description'
}
}
end
let_it_be(:attribute_mapping) do
{
title: { path: %w(alert name), type: 'string' },
description: { path: %w(alert desc), type: 'string' }
}
end
let_it_be(:integration) do
create(:alert_management_http_integration, project: project, payload_attribute_mapping: attribute_mapping)
end
describe '#title' do
subject { parsed_payload.title }
it { is_expected.to eq('mapped title') }
end
describe '#description' do
subject { parsed_payload.description }
it { is_expected.to eq('mapped description') }
end
end
context 'with inactive HTTP integration' do
let_it_be(:integration) do
create(:alert_management_http_integration, :inactive, project: project, payload_attribute_mapping: attribute_mapping)
end
it_behaves_like 'parsing alert payload fields with default paths'
end
context 'with blank custom mapping' do
let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
it_behaves_like 'parsing alert payload fields with default paths'
end
end
context 'with multiple_http_integrations_custom_mapping feature flag disabled' do
before do
stub_feature_flags(multiple_http_integrations_custom_mapping: false)
end
it_behaves_like 'parsing alert payload fields with default paths'
end
end
context 'with multiple HTTP integrations feature unavailable' do
before do
stub_licensed_features(multiple_alert_http_integrations: false)
end
it_behaves_like 'parsing alert payload fields with default paths'
end
end
describe '#gitlab_fingerprint' do
subject { parsed_payload.gitlab_fingerprint }
......
......@@ -17,13 +17,14 @@ module Gitlab
# @param project [Project]
# @param payload [Hash]
# @param monitoring_tool [String]
def parse(project, payload, monitoring_tool: nil)
# @param integration [AlertManagement::HttpIntegration]
def parse(project, payload, monitoring_tool: nil, integration: nil)
payload_class = payload_class_for(
monitoring_tool: monitoring_tool || payload&.dig('monitoring_tool'),
payload: payload
)
payload_class.new(project: project, payload: payload)
payload_class.new(project: project, payload: payload, integration: integration)
end
private
......
......@@ -12,7 +12,7 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
include Gitlab::Routing
attr_accessor :project, :payload
attr_accessor :project, :payload, :integration
# Any attribute expected to be specifically read from
# or derived from an alert payload should be defined.
......@@ -147,6 +147,7 @@ module Gitlab
end
end
# Overriden in EE::Gitlab::AlertManagement::Payload::Generic
def value_for_paths(paths)
target_path = paths.find { |path| payload&.dig(*path) }
......
......@@ -56,5 +56,20 @@ RSpec.describe Gitlab::AlertManagement::Payload do
it { is_expected.to be_a Gitlab::AlertManagement::Payload::Generic }
end
end
context 'with integration specified by caller' do
let(:integration) { instance_double(AlertManagement::HttpIntegration) }
subject { described_class.parse(project, payload, integration: integration) }
it 'passes an integration to a specific payload' do
expect(::Gitlab::AlertManagement::Payload::Generic)
.to receive(:new)
.with(project: project, payload: payload, integration: integration)
.and_call_original
subject
end
end
end
end
......@@ -36,7 +36,7 @@ RSpec.describe Projects::Alerting::NotifyService do
subject { service.execute(token, nil) }
shared_examples 'notifcations are handled correctly' do
shared_examples 'notifications are handled correctly' do
context 'with valid token' do
let(:token) { integration.token }
let(:incident_management_setting) { double(send_email?: email_enabled, create_issue?: issue_enabled, auto_close_incident?: auto_close_enabled) }
......@@ -85,6 +85,15 @@ RSpec.describe Projects::Alerting::NotifyService do
it_behaves_like 'creates an alert management alert'
it_behaves_like 'assigns the alert properties'
it 'passes the integration to alert processing' do
expect(Gitlab::AlertManagement::Payload)
.to receive(:parse)
.with(project, payload.to_h, integration: integration)
.and_call_original
subject
end
it 'creates a system note corresponding to alert creation' do
expect { subject }.to change(Note, :count).by(1)
expect(Note.last.note).to include(payload_raw.fetch(:monitoring_tool))
......@@ -259,7 +268,7 @@ RSpec.describe Projects::Alerting::NotifyService do
subject { service.execute(token, integration) }
it_behaves_like 'notifcations are handled correctly' do
it_behaves_like 'notifications are handled correctly' do
let(:source) { integration.name }
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