gitlab-org--gitlab-foss/app/models/concerns/incident_management/escalatable.rb

117 lines
3.4 KiB
Ruby

# frozen_string_literal: true
module IncidentManagement
# Shared functionality for a `#status` field, representing
# whether action is required. In EE, this corresponds
# to paging functionality with EscalationPolicies.
#
# This module is only responsible for setting the status and
# possible status-related timestamps (EX triggered_at/resolved_at)
# for the implementing class. The relationships between these
# values and other related timestamps/logic should be managed from
# the object class itself. (EX Alert#ended_at = Alert#resolved_at)
module Escalatable
extend ActiveSupport::Concern
STATUSES = {
triggered: 0,
acknowledged: 1,
resolved: 2,
ignored: 3
}.freeze
STATUS_DESCRIPTIONS = {
triggered: 'Investigation has not started',
acknowledged: 'Someone is actively investigating the problem',
resolved: 'The problem has been addressed',
ignored: 'No action will be taken'
}.freeze
OPEN_STATUSES = [:triggered, :acknowledged].freeze
included do
validates :status, presence: true
# Ascending sort order sorts statuses: Ignored > Resolved > Acknowledged > Triggered
# Descending sort order sorts statuses: Triggered > Acknowledged > Resolved > Ignored
# https://gitlab.com/gitlab-org/gitlab/-/issues/221242#what-is-the-expected-correct-behavior
scope :order_status, -> (sort_order) { order(status: sort_order == :asc ? :desc : :asc) }
scope :open, -> { with_status(OPEN_STATUSES) }
state_machine :status, initial: :triggered do
state :triggered, value: STATUSES[:triggered]
state :acknowledged, value: STATUSES[:acknowledged]
state :resolved, value: STATUSES[:resolved] do
validates :resolved_at, presence: true
end
state :ignored, value: STATUSES[:ignored]
state :triggered, :acknowledged, :ignored do
validates :resolved_at, absence: true
end
event :trigger do
transition any => :triggered
end
event :acknowledge do
transition any => :acknowledged
end
event :resolve do
transition any => :resolved
end
event :ignore do
transition any => :ignored
end
before_transition to: [:triggered, :acknowledged, :ignored] do |escalatable, _transition|
escalatable.resolved_at = nil
end
before_transition to: :resolved do |escalatable, transition|
resolved_at = transition.args.first
escalatable.resolved_at = resolved_at || Time.current
end
end
class << self
def status_value(name)
state_machine_statuses[name]
end
def status_name(raw_status)
state_machine_statuses.key(raw_status)
end
def status_names
@status_names ||= state_machine_statuses.keys
end
def open_status?(status)
OPEN_STATUSES.include?(status)
end
private
def state_machine_statuses
@state_machine_statuses ||= state_machines[:status].states.to_h { |s| [s.name, s.value] }
end
end
def status_event_for(status)
self.class.state_machines[:status].events.transitions_for(self, to: status.to_s.to_sym).first&.event
end
def open?
self.class.open_status?(status_name)
end
end
end
end
::IncidentManagement::Escalatable.prepend_mod