summaryrefslogtreecommitdiff
path: root/lib/gitlab/alert_management/payload/base.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/alert_management/payload/base.rb')
-rw-r--r--lib/gitlab/alert_management/payload/base.rb167
1 files changed, 167 insertions, 0 deletions
diff --git a/lib/gitlab/alert_management/payload/base.rb b/lib/gitlab/alert_management/payload/base.rb
new file mode 100644
index 00000000000..74e47e5226e
--- /dev/null
+++ b/lib/gitlab/alert_management/payload/base.rb
@@ -0,0 +1,167 @@
+# frozen_string_literal: true
+
+# Representation of a payload of an alert. Defines a constant
+# API so that payloads from various sources can be treated
+# identically. Subclasses should define how to parse payload
+# based on source of alert.
+module Gitlab
+ module AlertManagement
+ module Payload
+ class Base
+ include ActiveModel::Model
+ include Gitlab::Utils::StrongMemoize
+ include Gitlab::Routing
+
+ attr_accessor :project, :payload
+
+ # Any attribute expected to be specifically read from
+ # or derived from an alert payload should be defined.
+ EXPECTED_PAYLOAD_ATTRIBUTES = [
+ :alert_markdown,
+ :alert_title,
+ :annotations,
+ :description,
+ :ends_at,
+ :environment,
+ :environment_name,
+ :full_query,
+ :generator_url,
+ :gitlab_alert,
+ :gitlab_fingerprint,
+ :gitlab_prometheus_alert_id,
+ :gitlab_y_label,
+ :has_required_attributes?,
+ :hosts,
+ :metric_id,
+ :metrics_dashboard_url,
+ :monitoring_tool,
+ :resolved?,
+ :runbook,
+ :service,
+ :severity,
+ :starts_at,
+ :status,
+ :title
+ ].freeze
+
+ # Define expected API for a payload
+ EXPECTED_PAYLOAD_ATTRIBUTES.each do |key|
+ define_method(key) {}
+ end
+
+ # Defines a method which allows access to a given
+ # value within an alert payload
+ #
+ # @param key [Symbol] Name expected to be used to reference value
+ # @param paths [String, Array<String>, Array<Array<String>>,]
+ # List of (nested) keys at value can be found, the
+ # first to yield a result will be used
+ # @param type [Symbol] If value should be converted to another type,
+ # that should be specified here
+ # @param fallback [Proc] Block to be executed to yield a value if
+ # a value cannot be idenitied at any provided paths
+ # Example)
+ # attribute :title
+ # paths: [['title'],
+ # ['details', 'title']]
+ # fallback: Proc.new { 'New Alert' }
+ #
+ # The above sample definition will define a method
+ # called #title which will return the value from the
+ # payload under the key `title` if available, otherwise
+ # looking under `details.title`. If neither returns a
+ # value, the return value will be `'New Alert'`
+ def self.attribute(key, paths:, type: nil, fallback: -> { nil })
+ define_method(key) do
+ strong_memoize(key) do
+ paths = Array(paths).first.is_a?(String) ? [Array(paths)] : paths
+ value = value_for_paths(paths)
+ value = parse_value(value, type) if value
+
+ value.presence || fallback.call
+ end
+ end
+ end
+
+ # Attributes of an AlertManagement::Alert as read
+ # directly from a payload. Prefer accessing
+ # AlertManagement::Alert directly for read operations.
+ def alert_params
+ {
+ description: description,
+ ended_at: ends_at,
+ environment: environment,
+ fingerprint: gitlab_fingerprint,
+ hosts: Array(hosts),
+ monitoring_tool: monitoring_tool,
+ payload: payload,
+ project_id: project.id,
+ prometheus_alert: gitlab_alert,
+ service: service,
+ severity: severity,
+ started_at: starts_at,
+ title: title
+ }.transform_values(&:presence).compact
+ end
+
+ def gitlab_fingerprint
+ strong_memoize(:gitlab_fingerprint) do
+ next unless plain_gitlab_fingerprint
+
+ Gitlab::AlertManagement::Fingerprint.generate(plain_gitlab_fingerprint)
+ end
+ end
+
+ def environment
+ strong_memoize(:environment) do
+ next unless environment_name
+
+ EnvironmentsFinder
+ .new(project, nil, { name: environment_name })
+ .find
+ .first
+ end
+ end
+
+ def resolved?
+ status == 'resolved'
+ end
+
+ def has_required_attributes?
+ true
+ end
+
+ private
+
+ def plain_gitlab_fingerprint; end
+
+ def value_for_paths(paths)
+ target_path = paths.find { |path| payload&.dig(*path) }
+
+ payload&.dig(*target_path) if target_path
+ end
+
+ def parse_value(value, type)
+ case type
+ when :time
+ parse_time(value)
+ when :integer
+ parse_integer(value)
+ else
+ value
+ end
+ end
+
+ def parse_time(value)
+ Time.parse(value).utc
+ rescue ArgumentError
+ end
+
+ def parse_integer(value)
+ Integer(value)
+ rescue ArgumentError, TypeError
+ end
+ end
+ end
+ end
+end