diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 15:26:25 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 15:26:25 +0300 |
commit | a09983ae35713f5a2bbb100981116d31ce99826e (patch) | |
tree | 2ee2af7bd104d57086db360a7e6d8c9d5d43667a /app/services/alert_management | |
parent | 18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff) |
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'app/services/alert_management')
5 files changed, 215 insertions, 105 deletions
diff --git a/app/services/alert_management/alerts/todo/create_service.rb b/app/services/alert_management/alerts/todo/create_service.rb new file mode 100644 index 00000000000..87af943fdc2 --- /dev/null +++ b/app/services/alert_management/alerts/todo/create_service.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module AlertManagement + module Alerts + module Todo + class CreateService + # @param alert [AlertManagement::Alert] + # @param current_user [User] + def initialize(alert, current_user) + @alert = alert + @current_user = current_user + end + + def execute + return error_no_permissions unless allowed? + + todos = TodoService.new.mark_todo(alert, current_user) + todo = todos&.first + + return error_existing_todo unless todo + + success(todo) + end + + private + + attr_reader :alert, :current_user + + def allowed? + current_user&.can?(:update_alert_management_alert, alert) + end + + def error(message) + ServiceResponse.error(payload: { alert: alert, todo: nil }, message: message) + end + + def success(todo) + ServiceResponse.success(payload: { alert: alert, todo: todo }) + end + + def error_no_permissions + error(_('You have insufficient permissions to create a Todo for this alert')) + end + + def error_existing_todo + error(_('You already have pending todo for this alert')) + end + end + end + end +end diff --git a/app/services/alert_management/alerts/update_service.rb b/app/services/alert_management/alerts/update_service.rb index ffabbb37289..0b7216cd9f8 100644 --- a/app/services/alert_management/alerts/update_service.rb +++ b/app/services/alert_management/alerts/update_service.rb @@ -12,17 +12,20 @@ module AlertManagement @alert = alert @current_user = current_user @params = params + @param_errors = [] end def execute return error_no_permissions unless allowed? - return error_no_updates if params.empty? - filter_assignees + 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) - process_assignement(old_assignees) + handle_changes(old_assignees: old_assignees) success else @@ -32,16 +35,13 @@ module AlertManagement private - attr_reader :alert, :current_user, :params + attr_reader :alert, :current_user, :params, :param_errors + delegate :resolved?, to: :alert def allowed? current_user&.can?(:update_alert_management_alert, alert) end - def assignee_todo_allowed? - assignee&.can?(:read_alert_management_alert, alert) - end - def todo_service strong_memoize(:todo_service) do TodoService.new @@ -60,39 +60,122 @@ module AlertManagement error(_('You have no permissions')) end - def error_no_updates - error(_('Please provide attributes to update')) + def error_invalid_params + error(param_errors.to_sentence) + end + + def add_param_error(message) + param_errors << message + end + + def filter_params + param_errors << _('Please provide attributes to update') if params.empty? + + 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? - params[:assignees] = Array(assignee) + # 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 assignee - strong_memoize(:assignee) do - # Take first assignee while multiple are not currently supported - params[:assignees]&.first - end + def unauthorized_assignees? + params[:assignees]&.any? { |user| !user.can?(:read_alert_management_alert, alert) } end - def process_assignement(old_assignees) + def handle_assignement(old_assignees) assign_todo add_assignee_system_note(old_assignees) end def assign_todo - # Remove check in follow-up issue https://gitlab.com/gitlab-org/gitlab/-/issues/222672 - return unless assignee_todo_allowed? - todo_service.assign_alert(alert, current_user) 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 params[:status] + + status_event = AlertManagement::Alert::STATUS_EVENTS[status_key] + + unless status_event + param_errors << _('Invalid status') + return + end + + params[:status_event] = status_event + end + + def status_key + strong_memoize(:status_key) do + status = params.delete(:status) + AlertManagement::Alert::STATUSES.key(status) + end + 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_STATUSES.include?(status_key) + + 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 diff --git a/app/services/alert_management/create_alert_issue_service.rb b/app/services/alert_management/create_alert_issue_service.rb index beacd240b08..6ea3fd867ef 100644 --- a/app/services/alert_management/create_alert_issue_service.rb +++ b/app/services/alert_management/create_alert_issue_service.rb @@ -2,6 +2,8 @@ module AlertManagement class CreateAlertIssueService + include Gitlab::Utils::StrongMemoize + # @param alert [AlertManagement::Alert] # @param user [User] def initialize(alert, user) @@ -13,18 +15,20 @@ module AlertManagement return error_no_permissions unless allowed? return error_issue_already_exists if alert.issue - result = create_issue(alert, user, alert_payload) - @issue = result[:issue] + result = create_issue + issue = result.payload[:issue] + + return error(result.message, issue) if result.error? + return error(object_errors(alert), issue) unless associate_alert_with_issue(issue) - return error(result[:message]) if result[:status] == :error - return error(alert.errors.full_messages.to_sentence) unless update_alert_issue_id + SystemNoteService.new_alert_issue(alert, issue, user) - success + result end private - attr_reader :alert, :user, :issue + attr_reader :alert, :user delegate :project, to: :alert @@ -32,29 +36,36 @@ module AlertManagement user.can?(:create_issue, project) end - def create_issue(alert, user, alert_payload) - ::IncidentManagement::CreateIssueService - .new(project, alert_payload, user) - .execute(skip_settings_check: true) - end + def create_issue + label_result = find_or_create_incident_label - def alert_payload - if alert.prometheus? - alert.payload - else - Gitlab::Alerting::NotificationPayloadParser.call(alert.payload.to_h) - end + # Create an unlabelled issue if we couldn't create the label + # due to a race condition. + # See https://gitlab.com/gitlab-org/gitlab-foss/issues/65042 + extra_params = label_result.success? ? { label_ids: [label_result.payload[:label].id] } : {} + + issue = Issues::CreateService.new( + project, + user, + title: alert_presenter.title, + description: alert_presenter.issue_description, + **extra_params + ).execute + + return error(object_errors(issue), issue) unless issue.valid? + + success(issue) end - def update_alert_issue_id + def associate_alert_with_issue(issue) alert.update(issue_id: issue.id) end - def success + def success(issue) ServiceResponse.success(payload: { issue: issue }) end - def error(message) + def error(message, issue = nil) ServiceResponse.error(payload: { issue: issue }, message: message) end @@ -65,5 +76,19 @@ module AlertManagement def error_no_permissions error(_('You have no permissions')) end + + def alert_presenter + strong_memoize(:alert_presenter) do + alert.present + end + end + + def find_or_create_incident_label + IncidentManagement::CreateIncidentLabelService.new(project, user).execute + end + + def object_errors(object) + object.errors.full_messages.to_sentence + end end end diff --git a/app/services/alert_management/process_prometheus_alert_service.rb b/app/services/alert_management/process_prometheus_alert_service.rb index 90fcbd95e4b..573d3914c05 100644 --- a/app/services/alert_management/process_prometheus_alert_service.rb +++ b/app/services/alert_management/process_prometheus_alert_service.rb @@ -66,7 +66,11 @@ module AlertManagement def process_resolved_alert_management_alert return if am_alert.blank? - return if am_alert.resolve(ends_at) + + if am_alert.resolve(ends_at) + close_issue(am_alert.issue) + return + end logger.warn( message: 'Unable to update AlertManagement::Alert status to resolved', @@ -75,12 +79,22 @@ module AlertManagement ) end + def close_issue(issue) + return if issue.blank? || issue.closed? + + Issues::CloseService + .new(project, User.alert_bot) + .execute(issue, system_note: false) + + SystemNoteService.auto_resolve_prometheus_alert(issue, project, User.alert_bot) if issue.reset.closed? + end + def logger @logger ||= Gitlab::AppLogger end def am_alert - @am_alert ||= AlertManagement::Alert.for_fingerprint(project, gitlab_fingerprint).first + @am_alert ||= AlertManagement::Alert.not_resolved.for_fingerprint(project, gitlab_fingerprint).first end def bad_request diff --git a/app/services/alert_management/update_alert_status_service.rb b/app/services/alert_management/update_alert_status_service.rb deleted file mode 100644 index a7ebddb82e0..00000000000 --- a/app/services/alert_management/update_alert_status_service.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -module AlertManagement - class UpdateAlertStatusService - include Gitlab::Utils::StrongMemoize - - # @param alert [AlertManagement::Alert] - # @param user [User] - # @param status [Integer] Must match a value from AlertManagement::Alert::STATUSES - def initialize(alert, user, status) - @alert = alert - @user = user - @status = status - end - - def execute - return error_no_permissions unless allowed? - return error_invalid_status unless status_key - - if alert.update(status_event: status_event) - success - else - error(alert.errors.full_messages.to_sentence) - end - end - - private - - attr_reader :alert, :user, :status - - delegate :project, to: :alert - - def allowed? - user.can?(:update_alert_management_alert, project) - end - - def status_key - strong_memoize(:status_key) do - AlertManagement::Alert::STATUSES.key(status) - end - end - - def status_event - AlertManagement::Alert::STATUS_EVENTS[status_key] - end - - def success - ServiceResponse.success(payload: { alert: alert }) - end - - def error_no_permissions - error(_('You have no permissions')) - end - - def error_invalid_status - error(_('Invalid status')) - end - - def error(message) - ServiceResponse.error(payload: { alert: alert }, message: message) - end - end -end |