Welcome to mirror list, hosted at ThFree Co, Russian Federation.

update_service.rb « alerts « alert_management « services « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 7a9bcf2a52d7a220253fa0054a5cb31407f34e56 (plain)
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# 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)

        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

      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)
      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 } }
        ).execute(alert.issue)
      end

      def should_sync_to_incident?
        Feature.enabled?(:incident_escalations, project) &&
          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