1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
# frozen_string_literal: true
module AlertManagement
module Alerts
class UpdateService
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
@current_user = current_user
@params = params
@param_errors = []
@status = params.delete(:status)
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, :current_user, :params, :param_errors, :status
delegate :resolved?, to: :alert
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, 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 resolved?
end
def add_status_change_system_note
SystemNoteService.change_alert_status(alert, current_user)
end
def resolve_todos
todo_service.resolve_todos_for_target(alert, current_user)
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(alert.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(alert.project, open_alert)
{
link_start: '<a href="%{url}">'.html_safe % { url: alert_path },
link_end: '</a>'.html_safe
}
end
end
end
end
|