summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-23 12:09:47 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-23 12:09:47 +0000
commit8f9beefac3774b30e911fb00a68f4c7a5244cf27 (patch)
tree919c3a043f8c10bc3f78f3f6e029acfb6b972556 /app
parente4bf776a8829e5186a0f63603c0be627b891d80e (diff)
downloadgitlab-ce-8f9beefac3774b30e911fb00a68f4c7a5244cf27.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue1
-rw-r--r--app/assets/javascripts/project_select.js1
-rw-r--r--app/assets/javascripts/repository/components/breadcrumbs.vue4
-rw-r--r--app/assets/javascripts/repository/components/table/parent_row.vue4
-rw-r--r--app/finders/autocomplete/move_to_project_finder.rb2
-rw-r--r--app/finders/projects_finder.rb3
-rw-r--r--app/models/concerns/alert_event_lifecycle.rb50
-rw-r--r--app/models/concerns/optionally_search.rb4
-rw-r--r--app/models/concerns/protected_ref_access.rb1
-rw-r--r--app/models/concerns/select_for_project_authorization.rb3
-rw-r--r--app/models/environment.rb1
-rw-r--r--app/models/group.rb6
-rw-r--r--app/models/lfs_objects_project.rb2
-rw-r--r--app/models/member.rb4
-rw-r--r--app/models/project.rb9
-rw-r--r--app/models/project_group_link.rb1
-rw-r--r--app/models/project_team.rb9
-rw-r--r--app/models/prometheus_alert_event.rb43
-rw-r--r--app/models/self_managed_prometheus_alert_event.rb24
-rw-r--r--app/models/snippet_repository.rb22
-rw-r--r--app/models/user.rb3
-rw-r--r--app/serializers/prometheus_alert_entity.rb26
-rw-r--r--app/serializers/prometheus_alert_serializer.rb5
-rw-r--r--app/services/metrics/dashboard/update_dashboard_service.rb20
-rw-r--r--app/services/notes/create_service.rb87
-rw-r--r--app/services/notes/update_service.rb2
-rw-r--r--app/services/projects/prometheus/alerts/create_events_service.rb75
-rw-r--r--app/services/snippets/create_service.rb17
-rw-r--r--app/workers/all_queues.yml7
-rw-r--r--app/workers/incident_management/process_prometheus_alert_worker.rb88
30 files changed, 435 insertions, 89 deletions
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index 30f1e843e7b..8fd377938b4 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -64,6 +64,7 @@ export default {
this.groupId,
term,
{
+ search_namespaces: true,
with_issues_enabled: true,
with_shared: false,
include_subgroups: true,
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index 66ce1ab5659..15c7c09366c 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -54,6 +54,7 @@ const projectSelect = () => {
this.groupId,
query.term,
{
+ search_namespaces: true,
with_issues_enabled: this.withIssuesEnabled,
with_merge_requests_enabled: this.withMergeRequestsEnabled,
with_shared: this.withShared,
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue
index 03766c4877e..6c58f48dc74 100644
--- a/app/assets/javascripts/repository/components/breadcrumbs.vue
+++ b/app/assets/javascripts/repository/components/breadcrumbs.vue
@@ -134,7 +134,9 @@ export default {
},
{
attrs: {
- href: `${this.newBlobPath}/${this.currentPath ? escape(this.currentPath) : ''}`,
+ href: `${this.newBlobPath}/${
+ this.currentPath ? encodeURIComponent(this.currentPath) : ''
+ }`,
class: 'qa-new-file-option',
},
text: __('New file'),
diff --git a/app/assets/javascripts/repository/components/table/parent_row.vue b/app/assets/javascripts/repository/components/table/parent_row.vue
index a5c6c9822fb..f9fcbc356e8 100644
--- a/app/assets/javascripts/repository/components/table/parent_row.vue
+++ b/app/assets/javascripts/repository/components/table/parent_row.vue
@@ -25,10 +25,10 @@ export default {
const splitArray = this.path.split('/');
splitArray.pop();
- return splitArray.join('/');
+ return splitArray.map(p => encodeURIComponent(p)).join('/');
},
parentRoute() {
- return { path: `/-/tree/${escape(this.commitRef)}/${escape(this.parentPath)}` };
+ return { path: `/-/tree/${escape(this.commitRef)}/${this.parentPath}` };
},
},
methods: {
diff --git a/app/finders/autocomplete/move_to_project_finder.rb b/app/finders/autocomplete/move_to_project_finder.rb
index 491cce2232e..af6defc1fc6 100644
--- a/app/finders/autocomplete/move_to_project_finder.rb
+++ b/app/finders/autocomplete/move_to_project_finder.rb
@@ -25,7 +25,7 @@ module Autocomplete
def execute
current_user
.projects_where_can_admin_issues
- .optionally_search(search)
+ .optionally_search(search, include_namespace: true)
.excluding_project(project_id)
.eager_load_namespace_and_owner
.sorted_by_name_asc_limited(LIMIT)
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index c319d2fed87..961694bd91f 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -17,6 +17,7 @@
# tags: string[]
# personal: boolean
# search: string
+# search_namespaces: boolean
# non_archived: boolean
# archived: 'only' or boolean
# min_access_level: integer
@@ -171,7 +172,7 @@ class ProjectsFinder < UnionFinder
def by_search(items)
params[:search] ||= params[:name]
- params[:search].present? ? items.search(params[:search]) : items
+ items.optionally_search(params[:search], include_namespace: params[:search_namespaces].present?)
end
def by_deleted_status(items)
diff --git a/app/models/concerns/alert_event_lifecycle.rb b/app/models/concerns/alert_event_lifecycle.rb
new file mode 100644
index 00000000000..4d2b717ead2
--- /dev/null
+++ b/app/models/concerns/alert_event_lifecycle.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module AlertEventLifecycle
+ extend ActiveSupport::Concern
+
+ included do
+ validates :started_at, presence: true
+ validates :status, presence: true
+
+ state_machine :status, initial: :none do
+ state :none, value: nil
+
+ state :firing, value: 0 do
+ validates :payload_key, presence: true
+ validates :ended_at, absence: true
+ end
+
+ state :resolved, value: 1 do
+ validates :ended_at, presence: true
+ end
+
+ event :fire do
+ transition none: :firing
+ end
+
+ event :resolve do
+ transition firing: :resolved
+ end
+
+ before_transition to: :firing do |alert_event, transition|
+ started_at = transition.args.first
+ alert_event.started_at = started_at
+ end
+
+ before_transition to: :resolved do |alert_event, transition|
+ ended_at = transition.args.first
+ alert_event.ended_at = ended_at || Time.current
+ end
+ end
+
+ scope :firing, -> { where(status: status_value_for(:firing)) }
+ scope :resolved, -> { where(status: status_value_for(:resolved)) }
+
+ scope :count_by_project_id, -> { group(:project_id).count }
+
+ def self.status_value_for(name)
+ state_machines[:status].states[name].value
+ end
+ end
+end
diff --git a/app/models/concerns/optionally_search.rb b/app/models/concerns/optionally_search.rb
index 4093429e372..06f8c3dc1cb 100644
--- a/app/models/concerns/optionally_search.rb
+++ b/app/models/concerns/optionally_search.rb
@@ -12,8 +12,8 @@ module OptionallySearch
end
# Optionally limits a result set to those matching the given search query.
- def optionally_search(query = nil)
- query.present? ? search(query) : all
+ def optionally_search(query = nil, **options)
+ query.present? ? search(query, **options) : all
end
end
end
diff --git a/app/models/concerns/protected_ref_access.rb b/app/models/concerns/protected_ref_access.rb
index 01cb5a14762..7373f006d64 100644
--- a/app/models/concerns/protected_ref_access.rb
+++ b/app/models/concerns/protected_ref_access.rb
@@ -19,7 +19,6 @@ module ProtectedRefAccess
end
included do
- scope :master, -> { maintainer } # @deprecated
scope :maintainer, -> { where(access_level: Gitlab::Access::MAINTAINER) }
scope :developer, -> { where(access_level: Gitlab::Access::DEVELOPER) }
scope :by_user, -> (user) { where(user_id: user ) }
diff --git a/app/models/concerns/select_for_project_authorization.rb b/app/models/concerns/select_for_project_authorization.rb
index 333c9118aa5..4fae36f7b8d 100644
--- a/app/models/concerns/select_for_project_authorization.rb
+++ b/app/models/concerns/select_for_project_authorization.rb
@@ -11,8 +11,5 @@ module SelectForProjectAuthorization
def select_as_maintainer_for_project_authorization
select(["projects.id AS project_id", "#{Gitlab::Access::MAINTAINER} AS access_level"])
end
-
- # @deprecated
- alias_method :select_as_master_for_project_authorization, :select_as_maintainer_for_project_authorization
end
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 3f9247b1544..e9b1c55726d 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -14,6 +14,7 @@ class Environment < ApplicationRecord
has_many :successful_deployments, -> { success }, class_name: 'Deployment'
has_many :active_deployments, -> { active }, class_name: 'Deployment'
has_many :prometheus_alerts, inverse_of: :environment
+ has_many :self_managed_prometheus_alert_events, inverse_of: :environment
has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment'
has_one :last_deployable, through: :last_deployment, source: 'deployable', source_type: 'CommitStatus'
diff --git a/app/models/group.rb b/app/models/group.rb
index e9b3e3c3369..da69f7cc11e 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -245,9 +245,6 @@ class Group < Namespace
add_user(user, :maintainer, current_user: current_user)
end
- # @deprecated
- alias_method :add_master, :add_maintainer
-
def add_owner(user, current_user = nil)
add_user(user, :owner, current_user: current_user)
end
@@ -274,9 +271,6 @@ class Group < Namespace
::ContainerRepository.for_group_and_its_subgroups(self).exists?
end
- # @deprecated
- alias_method :has_master?, :has_maintainer?
-
# Check if user is a last owner of the group.
def last_owner?(user)
has_owner?(user) && members_with_parents.owners.size == 1
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index 68ef84223c5..e1966eda277 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class LfsObjectsProject < ApplicationRecord
+ include ::EachBatch
+
belongs_to :project
belongs_to :lfs_object
diff --git a/app/models/member.rb b/app/models/member.rb
index 99dee67346e..089efcb81dd 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -76,10 +76,8 @@ class Member < ApplicationRecord
scope :developers, -> { active.where(access_level: DEVELOPER) }
scope :maintainers, -> { active.where(access_level: MAINTAINER) }
scope :non_guests, -> { where('members.access_level > ?', GUEST) }
- scope :masters, -> { maintainers } # @deprecated
- scope :owners, -> { active.where(access_level: OWNER) }
+ scope :owners, -> { active.where(access_level: OWNER) }
scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) }
- scope :owners_and_masters, -> { owners_and_maintainers } # @deprecated
scope :with_user, -> (user) { where(user: user) }
scope :with_source_id, ->(source_id) { where(source_id: source_id) }
diff --git a/app/models/project.rb b/app/models/project.rb
index ffdd13b72d5..34c9c7320be 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -255,6 +255,8 @@ class Project < ApplicationRecord
has_many :prometheus_metrics
has_many :prometheus_alerts, inverse_of: :project
+ has_many :prometheus_alert_events, inverse_of: :project
+ has_many :self_managed_prometheus_alert_events, inverse_of: :project
# Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy
@@ -349,7 +351,6 @@ class Project < ApplicationRecord
delegate :members, to: :team, prefix: true
delegate :add_user, :add_users, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_role, to: :team
- delegate :add_master, to: :team # @deprecated
delegate :group_runners_enabled, :group_runners_enabled=, :group_runners_enabled?, to: :ci_cd_settings
delegate :root_ancestor, to: :namespace, allow_nil: true
delegate :last_pipeline, to: :commit, allow_nil: true
@@ -591,9 +592,9 @@ class Project < ApplicationRecord
# case-insensitive.
#
# query - The search query as a String.
- def search(query)
- if Feature.enabled?(:project_search_by_full_path, default_enabled: true)
- joins(:route).fuzzy_search(query, [Route.arel_table[:path], :name, :description])
+ def search(query, include_namespace: false)
+ if include_namespace && Feature.enabled?(:project_search_by_full_path, default_enabled: true)
+ joins(:route).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name], :description])
else
fuzzy_search(query, [:path, :name, :description])
end
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index bc16a34612a..b4071c6d4a6 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -7,7 +7,6 @@ class ProjectGroupLink < ApplicationRecord
REPORTER = 20
DEVELOPER = 30
MAINTAINER = 40
- MASTER = MAINTAINER # @deprecated
belongs_to :project
belongs_to :group
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index de1fc55ba93..072d281e5f8 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -25,9 +25,6 @@ class ProjectTeam
add_user(user, :maintainer, current_user: current_user)
end
- # @deprecated
- alias_method :add_master, :add_maintainer
-
def add_role(user, role, current_user: nil)
public_send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend
end
@@ -98,9 +95,6 @@ class ProjectTeam
@maintainers ||= fetch_members(Gitlab::Access::MAINTAINER)
end
- # @deprecated
- alias_method :masters, :maintainers
-
def owners
@owners ||=
if group
@@ -156,9 +150,6 @@ class ProjectTeam
max_member_access(user.id) == Gitlab::Access::MAINTAINER
end
- # @deprecated
- alias_method :master?, :maintainer?
-
# Checks if `user` is authorized for this project, with at least the
# `min_access_level` (if given).
def member?(user, min_access_level = Gitlab::Access::GUEST)
diff --git a/app/models/prometheus_alert_event.rb b/app/models/prometheus_alert_event.rb
new file mode 100644
index 00000000000..7e61f6d5e3c
--- /dev/null
+++ b/app/models/prometheus_alert_event.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class PrometheusAlertEvent < ApplicationRecord
+ include AlertEventLifecycle
+
+ belongs_to :project, optional: false, validate: true, inverse_of: :prometheus_alert_events
+ belongs_to :prometheus_alert, optional: false, validate: true, inverse_of: :prometheus_alert_events
+ has_and_belongs_to_many :related_issues, class_name: 'Issue', join_table: :issues_prometheus_alert_events # rubocop:disable Rails/HasAndBelongsToMany
+
+ validates :payload_key, uniqueness: { scope: :prometheus_alert_id }
+ validates :started_at, presence: true
+
+ delegate :title, :prometheus_metric_id, to: :prometheus_alert
+
+ scope :for_environment, -> (environment) do
+ joins(:prometheus_alert).where(prometheus_alerts: { environment_id: environment })
+ end
+
+ scope :with_prometheus_alert, -> { includes(:prometheus_alert) }
+
+ def self.last_by_project_id
+ ids = select(arel_table[:id].maximum.as('id')).group(:project_id).map(&:id)
+ with_prometheus_alert.find(ids)
+ end
+
+ def self.find_or_initialize_by_payload_key(project, alert, payload_key)
+ find_or_initialize_by(project: project, prometheus_alert: alert, payload_key: payload_key)
+ end
+
+ def self.find_by_payload_key(payload_key)
+ find_by(payload_key: payload_key)
+ end
+
+ def self.status_value_for(name)
+ state_machines[:status].states[name].value
+ end
+
+ def self.payload_key_for(gitlab_alert_id, started_at)
+ plain = [gitlab_alert_id, started_at].join('/')
+
+ Digest::SHA1.hexdigest(plain)
+ end
+end
diff --git a/app/models/self_managed_prometheus_alert_event.rb b/app/models/self_managed_prometheus_alert_event.rb
new file mode 100644
index 00000000000..d2d4a5c37d4
--- /dev/null
+++ b/app/models/self_managed_prometheus_alert_event.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class SelfManagedPrometheusAlertEvent < ApplicationRecord
+ include AlertEventLifecycle
+
+ belongs_to :project, validate: true, inverse_of: :self_managed_prometheus_alert_events
+ belongs_to :environment, validate: true, inverse_of: :self_managed_prometheus_alert_events
+ has_and_belongs_to_many :related_issues, class_name: 'Issue', join_table: :issues_self_managed_prometheus_alert_events # rubocop:disable Rails/HasAndBelongsToMany
+
+ validates :started_at, presence: true
+ validates :payload_key, uniqueness: { scope: :project_id }
+
+ def self.find_or_initialize_by_payload_key(project, payload_key)
+ find_or_initialize_by(project: project, payload_key: payload_key) do |event|
+ yield event if block_given?
+ end
+ end
+
+ def self.payload_key_for(started_at, alert_name, query_expression)
+ plain = [started_at, alert_name, query_expression].join('/')
+
+ Digest::SHA1.hexdigest(plain)
+ end
+end
diff --git a/app/models/snippet_repository.rb b/app/models/snippet_repository.rb
index f879f58b5a3..e60dbb4d141 100644
--- a/app/models/snippet_repository.rb
+++ b/app/models/snippet_repository.rb
@@ -48,15 +48,27 @@ class SnippetRepository < ApplicationRecord
next_index = get_last_empty_file_index + 1
files.each do |file_entry|
+ file_entry[:file_path] = file_path_for(file_entry, next_index) { next_index += 1 }
file_entry[:action] = infer_action(file_entry) unless file_entry[:action]
-
- if file_entry[:file_path].blank?
- file_entry[:file_path] = build_empty_file_name(next_index)
- next_index += 1
- end
end
end
+ def file_path_for(file_entry, next_index)
+ return file_entry[:file_path] if file_entry[:file_path].present?
+ return file_entry[:previous_path] if reuse_previous_path?(file_entry)
+
+ build_empty_file_name(next_index).tap { yield }
+ end
+
+ # If the user removed the file_path and the previous_path
+ # matches the EMPTY_FILE_PATTERN, we don't need to
+ # rename the file and build a new empty file name,
+ # we can just reuse the existing file name
+ def reuse_previous_path?(file_entry)
+ file_entry[:file_path].blank? &&
+ EMPTY_FILE_PATTERN.match?(file_entry[:previous_path])
+ end
+
def infer_action(file_entry)
return :create if file_entry[:previous_path].blank?
diff --git a/app/models/user.rb b/app/models/user.rb
index 48438d0e7e2..7789326e8fa 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1693,9 +1693,6 @@ class User < ApplicationRecord
end
end
- # @deprecated
- alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
-
protected
# override, from Devise::Validatable
diff --git a/app/serializers/prometheus_alert_entity.rb b/app/serializers/prometheus_alert_entity.rb
new file mode 100644
index 00000000000..413be511903
--- /dev/null
+++ b/app/serializers/prometheus_alert_entity.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class PrometheusAlertEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id
+ expose :title
+ expose :query
+ expose :threshold
+
+ expose :operator do |prometheus_alert|
+ prometheus_alert.computed_operator
+ end
+
+ expose :alert_path do |prometheus_alert|
+ project_prometheus_alert_path(prometheus_alert.project, prometheus_alert.prometheus_metric_id, environment_id: prometheus_alert.environment.id, format: :json)
+ end
+
+ private
+
+ alias_method :prometheus_alert, :object
+
+ def can_read_prometheus_alerts?
+ can?(request.current_user, :read_prometheus_alerts, prometheus_alert.project)
+ end
+end
diff --git a/app/serializers/prometheus_alert_serializer.rb b/app/serializers/prometheus_alert_serializer.rb
new file mode 100644
index 00000000000..4dafb7216db
--- /dev/null
+++ b/app/serializers/prometheus_alert_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class PrometheusAlertSerializer < BaseSerializer
+ entity PrometheusAlertEntity
+end
diff --git a/app/services/metrics/dashboard/update_dashboard_service.rb b/app/services/metrics/dashboard/update_dashboard_service.rb
index 65e6e195f79..25a727ad44c 100644
--- a/app/services/metrics/dashboard/update_dashboard_service.rb
+++ b/app/services/metrics/dashboard/update_dashboard_service.rb
@@ -12,7 +12,8 @@ module Metrics
steps :check_push_authorized,
:check_branch_name,
:check_file_type,
- :update_file
+ :update_file,
+ :create_merge_request
def execute
execute_steps
@@ -49,6 +50,23 @@ module Metrics
end
end
+ def create_merge_request(result)
+ return success(result) if project.default_branch == branch
+
+ merge_request_params = {
+ source_branch: branch,
+ target_branch: project.default_branch,
+ title: params[:commit_message]
+ }
+ merge_request = ::MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
+
+ if merge_request.persisted?
+ success(result.merge(merge_request: Gitlab::UrlBuilder.build(merge_request)))
+ else
+ error(merge_request.errors.full_messages.join(','), :bad_request)
+ end
+ end
+
def push_authorized?
Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(branch)
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 4a0d85038ee..80bc4485988 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -17,57 +17,72 @@ module Notes
# We execute commands (extracted from `params[:note]`) on the noteable
# **before** we save the note because if the note consists of commands
# only, there is no need be create a note!
- quick_actions_service = QuickActionsService.new(project, current_user)
- if quick_actions_service.supported?(note)
- content, update_params, message = quick_actions_service.execute(note, quick_action_options)
+ execute_quick_actions(note) do |only_commands|
+ note.run_after_commit do
+ # Finish the harder work in the background
+ NewNoteWorker.perform_async(note.id)
+ end
- only_commands = content.empty?
+ note_saved = note.with_transaction_returning_status do
+ !only_commands && note.save
+ end
- note.note = content
+ when_saved(note) if note_saved
end
- note.run_after_commit do
- # Finish the harder work in the background
- NewNoteWorker.perform_async(note.id)
- end
+ note
+ end
- note_saved = note.with_transaction_returning_status do
- !only_commands && note.save
- end
+ private
- if note_saved
- if note.part_of_discussion? && note.discussion.can_convert_to_discussion?
- note.discussion.convert_to_discussion!(save: true)
- end
+ def execute_quick_actions(note)
+ return yield(false) unless quick_actions_service.supported?(note)
- todo_service.new_note(note, current_user)
- clear_noteable_diffs_cache(note)
- Suggestions::CreateService.new(note).execute
- increment_usage_counter(note)
+ content, update_params, message = quick_actions_service.execute(note, quick_action_options)
+ only_commands = content.empty?
+ note.note = content
- if Feature.enabled?(:notes_create_service_tracking, project)
- Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note))
- end
- end
+ yield(only_commands)
- if quick_actions_service.commands_executed_count.to_i > 0
- if update_params.present?
- quick_actions_service.apply_updates(update_params, note)
- note.commands_changes = update_params
- end
+ do_commands(note, update_params, message, only_commands)
+ end
- # We must add the error after we call #save because errors are reset
- # when #save is called
- if only_commands
- note.errors.add(:commands_only, message.presence || _('Failed to apply commands.'))
- end
+ def quick_actions_service
+ @quick_actions_service ||= QuickActionsService.new(project, current_user)
+ end
+
+ def when_saved(note)
+ if note.part_of_discussion? && note.discussion.can_convert_to_discussion?
+ note.discussion.convert_to_discussion!(save: true)
end
- note
+ todo_service.new_note(note, current_user)
+ clear_noteable_diffs_cache(note)
+ Suggestions::CreateService.new(note).execute
+ increment_usage_counter(note)
+
+ if Feature.enabled?(:notes_create_service_tracking, project)
+ Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note))
+ end
end
- private
+ def do_commands(note, update_params, message, only_commands)
+ return if quick_actions_service.commands_executed_count.to_i.zero?
+
+ if update_params.present?
+ quick_actions_service.apply_updates(update_params, note)
+ note.commands_changes = update_params
+ end
+
+ # We must add the error after we call #save because errors are reset
+ # when #save is called
+ if only_commands
+ note.errors.add(:commands_only, message.presence || _('Failed to apply commands.'))
+ # Allow consumers to detect problems applying commands
+ note.errors.add(:commands, _('Failed to apply commands.')) unless message.present?
+ end
+ end
# EE::Notes::CreateService would override this method
def quick_action_options
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 3070e7b0e53..ed08f693901 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -55,6 +55,8 @@ module Notes
# We must add the error after we call #save because errors are reset
# when #save is called
note.errors.add(:commands_only, message.presence || _('Commands did not apply'))
+ # Allow consumers to detect problems applying commands
+ note.errors.add(:commands, _('Commands did not apply')) unless message.present?
Notes::DestroyService.new(project, current_user).execute(note)
end
diff --git a/app/services/projects/prometheus/alerts/create_events_service.rb b/app/services/projects/prometheus/alerts/create_events_service.rb
new file mode 100644
index 00000000000..a29240947ff
--- /dev/null
+++ b/app/services/projects/prometheus/alerts/create_events_service.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Projects
+ module Prometheus
+ module Alerts
+ # Persists a series of Prometheus alert events as list of PrometheusAlertEvent.
+ class CreateEventsService < BaseService
+ def execute
+ create_events_from(alerts)
+ end
+
+ private
+
+ def create_events_from(alerts)
+ Array.wrap(alerts).map { |alert| create_event(alert) }.compact
+ end
+
+ def create_event(payload)
+ parsed_alert = Gitlab::Alerting::Alert.new(project: project, payload: payload)
+
+ return unless parsed_alert.valid?
+
+ if parsed_alert.gitlab_managed?
+ create_managed_prometheus_alert_event(parsed_alert)
+ else
+ create_self_managed_prometheus_alert_event(parsed_alert)
+ end
+ end
+
+ def alerts
+ params['alerts']
+ end
+
+ def find_alert(metric)
+ Projects::Prometheus::AlertsFinder
+ .new(project: project, metric: metric)
+ .execute
+ .first
+ end
+
+ def create_managed_prometheus_alert_event(parsed_alert)
+ alert = find_alert(parsed_alert.metric_id)
+ payload_key = PrometheusAlertEvent.payload_key_for(parsed_alert.metric_id, parsed_alert.starts_at_raw)
+
+ event = PrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, alert, payload_key)
+
+ set_status(parsed_alert, event)
+ end
+
+ def create_self_managed_prometheus_alert_event(parsed_alert)
+ payload_key = SelfManagedPrometheusAlertEvent.payload_key_for(parsed_alert.starts_at_raw, parsed_alert.title, parsed_alert.full_query)
+
+ event = SelfManagedPrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, payload_key) do |event|
+ event.environment = parsed_alert.environment
+ event.title = parsed_alert.title
+ event.query_expression = parsed_alert.full_query
+ end
+
+ set_status(parsed_alert, event)
+ end
+
+ def set_status(parsed_alert, event)
+ persisted = case parsed_alert.status
+ when 'firing'
+ event.fire(parsed_alert.starts_at)
+ when 'resolved'
+ event.resolve(parsed_alert.ends_at)
+ end
+
+ event if persisted
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/snippets/create_service.rb b/app/services/snippets/create_service.rb
index 2998208f50b..9178b929656 100644
--- a/app/services/snippets/create_service.rb
+++ b/app/services/snippets/create_service.rb
@@ -38,19 +38,16 @@ module Snippets
private
def save_and_commit(snippet)
- result = snippet.with_transaction_returning_status do
- (snippet.save && snippet.store_mentions!).tap do |saved|
- break false unless saved
-
- if Feature.enabled?(:version_snippets, current_user)
- create_repository_for(snippet)
- end
- end
+ snippet_saved = snippet.with_transaction_returning_status do
+ snippet.save && snippet.store_mentions!
end
- create_commit(snippet) if result && snippet.repository_exists?
+ if snippet_saved && Feature.enabled?(:version_snippets, current_user)
+ create_repository_for(snippet)
+ create_commit(snippet)
+ end
- result
+ snippet_saved
rescue => e # Rescuing all because we can receive Creation exceptions, GRPC exceptions, Git exceptions, ...
snippet.errors.add(:base, e.message)
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index dd0eeaa9359..19158e7173c 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -528,6 +528,13 @@
:resource_boundary: :unknown
:weight: 2
:idempotent:
+- :name: incident_management:incident_management_process_prometheus_alert
+ :feature_category: :incident_management
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :cpu
+ :weight: 2
+ :idempotent:
- :name: jira_importer:jira_import_advance_stage
:feature_category: :importers
:has_external_dependencies:
diff --git a/app/workers/incident_management/process_prometheus_alert_worker.rb b/app/workers/incident_management/process_prometheus_alert_worker.rb
new file mode 100644
index 00000000000..768e049c60e
--- /dev/null
+++ b/app/workers/incident_management/process_prometheus_alert_worker.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+ class ProcessPrometheusAlertWorker # rubocop:disable Scalability/IdempotentWorker
+ include ApplicationWorker
+
+ queue_namespace :incident_management
+ feature_category :incident_management
+ worker_resource_boundary :cpu
+
+ def perform(project_id, alert_hash)
+ project = find_project(project_id)
+ return unless project
+
+ parsed_alert = Gitlab::Alerting::Alert.new(project: project, payload: alert_hash)
+ event = find_prometheus_alert_event(parsed_alert)
+
+ if event&.resolved?
+ issue = event.related_issues.order_created_at_desc.detect(&:opened?)
+
+ close_issue(project, issue)
+ else
+ issue = create_issue(project, alert_hash)
+
+ relate_issue_to_event(event, issue)
+ end
+ end
+
+ private
+
+ def find_project(project_id)
+ Project.find_by_id(project_id)
+ end
+
+ def find_prometheus_alert_event(alert)
+ if alert.gitlab_managed?
+ find_gitlab_managed_event(alert)
+ else
+ find_self_managed_event(alert)
+ end
+ end
+
+ def find_gitlab_managed_event(alert)
+ payload_key = payload_key_for_alert(alert)
+
+ PrometheusAlertEvent.find_by_payload_key(payload_key)
+ end
+
+ def find_self_managed_event(alert)
+ payload_key = payload_key_for_alert(alert)
+
+ SelfManagedPrometheusAlertEvent.find_by_payload_key(payload_key)
+ end
+
+ def payload_key_for_alert(alert)
+ if alert.gitlab_managed?
+ PrometheusAlertEvent.payload_key_for(alert.metric_id, alert.starts_at_raw)
+ else
+ SelfManagedPrometheusAlertEvent.payload_key_for(alert.starts_at_raw, alert.title, alert.full_query)
+ end
+ end
+
+ def create_issue(project, alert)
+ IncidentManagement::CreateIssueService
+ .new(project, alert)
+ .execute
+ .dig(:issue)
+ end
+
+ def close_issue(project, issue)
+ return if issue.blank? || issue.closed?
+
+ processed_issue = Issues::CloseService
+ .new(project, User.alert_bot)
+ .execute(issue, system_note: false)
+
+ SystemNoteService.auto_resolve_prometheus_alert(issue, project, User.alert_bot) if processed_issue.reset.closed?
+ end
+
+ def relate_issue_to_event(event, issue)
+ return unless event && issue
+
+ if event.related_issues.exclude?(issue)
+ event.related_issues << issue
+ end
+ end
+ end
+end