202 lines
5.5 KiB
Ruby
202 lines
5.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module AlertManagement
|
|
module Alerts
|
|
class UpdateService < ::BaseProjectService
|
|
include Gitlab::Utils::StrongMemoize
|
|
|
|
# @param alert [AlertManagement::Alert]
|
|
# @param current_user [User]
|
|
# @param params [Hash] Attributes of the alert
|
|
def initialize(alert, current_user, params)
|
|
@alert = alert
|
|
@param_errors = []
|
|
@status = params.delete(:status)
|
|
@status_change_reason = params.delete(:status_change_reason)
|
|
|
|
super(project: alert.project, current_user: current_user, params: params)
|
|
end
|
|
|
|
def execute
|
|
return error_no_permissions unless allowed?
|
|
|
|
filter_params
|
|
return error_invalid_params if param_errors.any?
|
|
|
|
# Save old assignees for system notes
|
|
old_assignees = alert.assignees.to_a
|
|
|
|
if alert.update(params)
|
|
handle_changes(old_assignees: old_assignees)
|
|
|
|
success
|
|
else
|
|
error(alert.errors.full_messages.to_sentence)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :alert, :param_errors, :status, :status_change_reason
|
|
|
|
def allowed?
|
|
current_user&.can?(:update_alert_management_alert, alert)
|
|
end
|
|
|
|
def todo_service
|
|
strong_memoize(:todo_service) do
|
|
TodoService.new
|
|
end
|
|
end
|
|
|
|
def success
|
|
ServiceResponse.success(payload: { alert: alert })
|
|
end
|
|
|
|
def error(message)
|
|
ServiceResponse.error(payload: { alert: alert }, message: message)
|
|
end
|
|
|
|
def error_no_permissions
|
|
error(_('You have no permissions'))
|
|
end
|
|
|
|
def error_invalid_params
|
|
error(param_errors.to_sentence)
|
|
end
|
|
|
|
def add_param_error(message)
|
|
param_errors << message
|
|
end
|
|
|
|
def param_errors?
|
|
params.empty? && status.blank?
|
|
end
|
|
|
|
def filter_params
|
|
param_errors << _('Please provide attributes to update') if param_errors?
|
|
|
|
filter_status
|
|
filter_assignees
|
|
filter_duplicate
|
|
end
|
|
|
|
def handle_changes(old_assignees:)
|
|
handle_assignement(old_assignees) if params[:assignees]
|
|
handle_status_change if params[:status_event]
|
|
end
|
|
|
|
# ----- Assignee-related behavior ------
|
|
def filter_assignees
|
|
return if params[:assignees].nil?
|
|
|
|
# Always take first assignee while multiple are not currently supported
|
|
params[:assignees] = Array(params[:assignees].first)
|
|
|
|
param_errors << _('Assignee has no permissions') if unauthorized_assignees?
|
|
end
|
|
|
|
def unauthorized_assignees?
|
|
params[:assignees]&.any? { |user| !user.can?(:read_alert_management_alert, alert) }
|
|
end
|
|
|
|
def handle_assignement(old_assignees)
|
|
assign_todo(old_assignees)
|
|
add_assignee_system_note(old_assignees)
|
|
end
|
|
|
|
def assign_todo(old_assignees)
|
|
todo_service.reassigned_assignable(alert, current_user, old_assignees)
|
|
end
|
|
|
|
def add_assignee_system_note(old_assignees)
|
|
SystemNoteService.change_issuable_assignees(alert, project, current_user, old_assignees)
|
|
end
|
|
|
|
# ------ Status-related behavior -------
|
|
def filter_status
|
|
return unless status
|
|
|
|
status_event = alert.status_event_for(status)
|
|
|
|
unless status_event
|
|
param_errors << _('Invalid status')
|
|
return
|
|
end
|
|
|
|
params[:status_event] = status_event
|
|
end
|
|
|
|
def handle_status_change
|
|
add_status_change_system_note
|
|
resolve_todos if alert.resolved?
|
|
sync_to_incident if should_sync_to_incident?
|
|
end
|
|
|
|
def add_status_change_system_note
|
|
SystemNoteService.change_alert_status(alert, current_user, status_change_reason)
|
|
end
|
|
|
|
def resolve_todos
|
|
todo_service.resolve_todos_for_target(alert, current_user)
|
|
end
|
|
|
|
def sync_to_incident
|
|
::Issues::UpdateService.new(
|
|
project: project,
|
|
current_user: current_user,
|
|
params: {
|
|
escalation_status: {
|
|
status: status,
|
|
status_change_reason: " by changing the status of #{alert.to_reference(project)}"
|
|
}
|
|
}
|
|
).execute(alert.issue)
|
|
end
|
|
|
|
def should_sync_to_incident?
|
|
alert.issue &&
|
|
alert.issue.supports_escalation? &&
|
|
alert.issue.escalation_status &&
|
|
alert.issue.escalation_status.status != alert.status
|
|
end
|
|
|
|
def filter_duplicate
|
|
# Only need to check if changing to an open status
|
|
return unless params[:status_event] && AlertManagement::Alert.open_status?(status)
|
|
|
|
param_errors << unresolved_alert_error if duplicate_alert?
|
|
end
|
|
|
|
def duplicate_alert?
|
|
return if alert.fingerprint.blank?
|
|
|
|
open_alerts.any? && open_alerts.exclude?(alert)
|
|
end
|
|
|
|
def open_alerts
|
|
strong_memoize(:open_alerts) do
|
|
AlertManagement::Alert.for_fingerprint(project, alert.fingerprint).open
|
|
end
|
|
end
|
|
|
|
def unresolved_alert_error
|
|
_('An %{link_start}alert%{link_end} with the same fingerprint is already open. ' \
|
|
'To change the status of this alert, resolve the linked alert.'
|
|
) % open_alert_url_params
|
|
end
|
|
|
|
def open_alert_url_params
|
|
open_alert = open_alerts.first
|
|
alert_path = Gitlab::Routing.url_helpers.details_project_alert_management_path(project, open_alert)
|
|
|
|
{
|
|
link_start: '<a href="%{url}">'.html_safe % { url: alert_path },
|
|
link_end: '</a>'.html_safe
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
AlertManagement::Alerts::UpdateService.prepend_mod
|