Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1086ac5177
commit
26879909dd
62 changed files with 590 additions and 1482 deletions
|
@ -34,6 +34,13 @@
|
|||
- tmp/rubocop_cache/
|
||||
policy: pull
|
||||
|
||||
.coverage-cache:
|
||||
cache:
|
||||
key: "coverage-cache-v1"
|
||||
paths:
|
||||
- vendor/ruby/
|
||||
policy: pull
|
||||
|
||||
.qa-cache:
|
||||
cache:
|
||||
key: "qa-v1"
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
stage: test
|
||||
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
|
||||
script:
|
||||
# Only install knapsack after bundle install! Otherwise oddly some native
|
||||
# gems could not be found under some circumstance. No idea why, hours wasted.
|
||||
- run_timed_command "gem install knapsack --no-document"
|
||||
- run_timed_command "scripts/gitaly-test-build"
|
||||
- run_timed_command "scripts/gitaly-test-spawn"
|
||||
- source scripts/rspec_helpers.sh
|
||||
|
@ -32,6 +35,9 @@
|
|||
.rspec-base-migration:
|
||||
extends: .rails:rules:ee-and-foss-migration
|
||||
script:
|
||||
# Only install knapsack after bundle install! Otherwise oddly some native
|
||||
# gems could not be found under some circumstance. No idea why, hours wasted.
|
||||
- run_timed_command "gem install knapsack --no-document"
|
||||
- run_timed_command "scripts/gitaly-test-build"
|
||||
- run_timed_command "scripts/gitaly-test-spawn"
|
||||
- source scripts/rspec_helpers.sh
|
||||
|
@ -67,6 +73,9 @@
|
|||
.rspec-ee-base-geo:
|
||||
extends: .rspec-base
|
||||
script:
|
||||
# Only install knapsack after bundle install! Otherwise oddly some native
|
||||
# gems could not be found under some circumstance. No idea why, hours wasted.
|
||||
- run_timed_command "gem install knapsack --no-document"
|
||||
- run_timed_command "scripts/gitaly-test-build"
|
||||
- run_timed_command "scripts/gitaly-test-spawn"
|
||||
- source scripts/rspec_helpers.sh
|
||||
|
@ -160,6 +169,25 @@ update-rails-cache:
|
|||
cache:
|
||||
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
|
||||
|
||||
.coverage-base:
|
||||
extends:
|
||||
- .default-retry
|
||||
- .default-before_script
|
||||
- .coverage-cache
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
|
||||
update-coverage-cache:
|
||||
extends:
|
||||
- .coverage-base
|
||||
- .shared:rules:update-cache
|
||||
stage: prepare
|
||||
script:
|
||||
- run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --without default development test production puma unicorn kerberos metrics omnibus ed25519"
|
||||
cache:
|
||||
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
|
||||
|
||||
.static-analysis-base:
|
||||
extends:
|
||||
- .default-retry
|
||||
|
@ -178,7 +206,7 @@ update-static-analysis-cache:
|
|||
script:
|
||||
- rm -rf ./node_modules # We remove node_modules because there's no mechanism to remove stall entries.
|
||||
- run_timed_command "retry yarn install --frozen-lockfile"
|
||||
- bundle exec rubocop --parallel # For the moment we only cache `vendor/ruby/`, `node_modules/`, and `tmp/rubocop_cache` so we don't need to run all the tasks,
|
||||
- run_timed_command "bundle exec rubocop --parallel" # For the moment we only cache `vendor/ruby/`, `node_modules/`, and `tmp/rubocop_cache` so we don't need to run all the tasks,
|
||||
cache:
|
||||
# We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up but RuboCop has a mechanism
|
||||
# for keeping only the N latest cache files, so we take advantage of it with `pull-push` and removing `node_modules` at the start of the job.
|
||||
|
@ -313,7 +341,7 @@ db:backup_and_restore:
|
|||
|
||||
rspec:coverage:
|
||||
extends:
|
||||
- .rails-job-base
|
||||
- .coverage-base
|
||||
- .rails:rules:rspec-coverage
|
||||
stage: post-test
|
||||
# We cannot use needs since it would mean needing 84 jobs (since most are parallelized)
|
||||
|
@ -333,11 +361,10 @@ rspec:coverage:
|
|||
- rspec-ee system pg11 geo
|
||||
- memory-static
|
||||
- memory-on-boot
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
script:
|
||||
- bundle exec scripts/merge-simplecov
|
||||
- bundle exec scripts/gather-test-memory-data
|
||||
- run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --without default development test production puma unicorn kerberos metrics omnibus ed25519"
|
||||
- run_timed_command "bundle exec scripts/merge-simplecov"
|
||||
- run_timed_command "bundle exec scripts/gather-test-memory-data"
|
||||
coverage: '/LOC \((\d+\.\d+%)\) covered.$/'
|
||||
artifacts:
|
||||
name: coverage
|
||||
|
|
7
Gemfile
7
Gemfile
|
@ -375,8 +375,6 @@ group :development, :test do
|
|||
|
||||
gem 'scss_lint', '~> 0.56.0', require: false
|
||||
gem 'haml_lint', '~> 0.34.0', require: false
|
||||
gem 'simplecov', '~> 0.18.5', require: false
|
||||
gem 'simplecov-cobertura', '~> 1.3.1', require: false
|
||||
gem 'bundler-audit', '~> 0.6.1', require: false
|
||||
|
||||
gem 'benchmark-ips', '~> 2.3.0', require: false
|
||||
|
@ -394,6 +392,11 @@ group :development, :test do
|
|||
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
|
||||
end
|
||||
|
||||
group :development, :test, :coverage do
|
||||
gem 'simplecov', '~> 0.18.5', require: false
|
||||
gem 'simplecov-cobertura', '~> 1.3.1', require: false
|
||||
end
|
||||
|
||||
# Gems required in omnibus-gitlab pipeline
|
||||
group :development, :test, :omnibus do
|
||||
gem 'license_finder', '~> 5.4', require: false
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlButton, GlIcon, GlLink, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui';
|
||||
import PackageTags from './package_tags.vue';
|
||||
import PackagePath from './package_path.vue';
|
||||
import PublishMethod from './publish_method.vue';
|
||||
import { getPackageTypeLabel } from '../utils';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
|
@ -15,6 +16,7 @@ export default {
|
|||
GlSprintf,
|
||||
GlTruncate,
|
||||
PackageTags,
|
||||
PackagePath,
|
||||
PublishMethod,
|
||||
ListItem,
|
||||
},
|
||||
|
@ -92,22 +94,12 @@ export default {
|
|||
</gl-sprintf>
|
||||
</div>
|
||||
|
||||
<div v-if="hasProjectLink" class="gl-display-flex gl-align-items-center">
|
||||
<gl-icon name="review-list" class="gl-ml-3 gl-mr-2 gl-min-w-0" />
|
||||
|
||||
<gl-link
|
||||
class="gl-text-body gl-min-w-0"
|
||||
data-testid="packages-row-project"
|
||||
:href="`/${packageEntity.project_path}`"
|
||||
>
|
||||
<gl-truncate :text="packageEntity.projectPathName" />
|
||||
</gl-link>
|
||||
</div>
|
||||
|
||||
<div v-if="showPackageType" class="d-flex align-items-center" data-testid="package-type">
|
||||
<gl-icon name="package" class="gl-ml-3 gl-mr-2" />
|
||||
<span>{{ packageType }}</span>
|
||||
</div>
|
||||
|
||||
<package-path v-if="hasProjectLink" :path="packageEntity.project_path" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
<script>
|
||||
import { GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
|
||||
export default {
|
||||
name: 'PackagePath',
|
||||
components: {
|
||||
GlIcon,
|
||||
GlLink,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
path: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
pathPieces() {
|
||||
return this.path.split('/');
|
||||
},
|
||||
root() {
|
||||
// we skip the first part of the path since is the 'base' group
|
||||
return this.pathPieces[1];
|
||||
},
|
||||
rootLink() {
|
||||
return joinPaths(this.pathPieces[0], this.root);
|
||||
},
|
||||
leaf() {
|
||||
return this.pathPieces[this.pathPieces.length - 1];
|
||||
},
|
||||
deeplyNested() {
|
||||
return this.pathPieces.length > 3;
|
||||
},
|
||||
hasGroup() {
|
||||
return this.root !== this.leaf;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-display-flex gl-align-items-center">
|
||||
<gl-icon data-testid="base-icon" name="project" class="gl-mx-3 gl-min-w-0" />
|
||||
|
||||
<gl-link data-testid="root-link" class="gl-text-gray-500 gl-min-w-0" :href="`/${rootLink}`">
|
||||
{{ root }}
|
||||
</gl-link>
|
||||
|
||||
<template v-if="hasGroup">
|
||||
<gl-icon data-testid="root-chevron" name="chevron-right" class="gl-mx-2 gl-min-w-0" />
|
||||
|
||||
<template v-if="deeplyNested">
|
||||
<span
|
||||
v-gl-tooltip="{ title: path }"
|
||||
data-testid="ellipsis-icon"
|
||||
class="gl-inset-border-1-gray-200 gl-rounded-base gl-px-2 gl-min-w-0"
|
||||
>
|
||||
<gl-icon name="ellipsis_h" />
|
||||
</span>
|
||||
<gl-icon data-testid="ellipsis-chevron" name="chevron-right" class="gl-mx-2 gl-min-w-0" />
|
||||
</template>
|
||||
|
||||
<gl-link data-testid="leaf-link" class="gl-text-gray-500 gl-min-w-0" :href="`/${path}`">
|
||||
{{ leaf }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
|
@ -56,16 +56,14 @@ module Emails
|
|||
subject: @message.subject)
|
||||
end
|
||||
|
||||
def prometheus_alert_fired_email(project_id, user_id, alert_payload)
|
||||
def prometheus_alert_fired_email(project_id, user_id, alert_attributes)
|
||||
@project = ::Project.find(project_id)
|
||||
user = ::User.find(user_id)
|
||||
|
||||
@alert = ::Gitlab::Alerting::Alert
|
||||
.new(project: @project, payload: alert_payload)
|
||||
.present
|
||||
return unless @alert.valid?
|
||||
@alert = AlertManagement::Alert.new(alert_attributes.with_indifferent_access).present
|
||||
return unless @alert.parsed_payload.has_required_attributes?
|
||||
|
||||
subject_text = "Alert: #{@alert.full_title}"
|
||||
subject_text = "Alert: #{@alert.email_title}"
|
||||
mail(to: user.notification_email_for(@project.group), subject: subject(subject_text))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -191,7 +191,7 @@ module AlertManagement
|
|||
end
|
||||
|
||||
def prometheus?
|
||||
monitoring_tool == Gitlab::AlertManagement::AlertParams::MONITORING_TOOLS[:prometheus]
|
||||
monitoring_tool == Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus]
|
||||
end
|
||||
|
||||
def register_new_event!
|
||||
|
|
|
@ -11,6 +11,7 @@ module Clusters
|
|||
RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze
|
||||
|
||||
self.table_name = 'cluster_platforms_kubernetes'
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
|
||||
belongs_to :cluster, inverse_of: :platform_kubernetes, class_name: 'Clusters::Cluster'
|
||||
|
||||
|
|
|
@ -182,7 +182,7 @@ module Issuable
|
|||
end
|
||||
|
||||
def supports_time_tracking?
|
||||
is_a?(TimeTrackable) && !incident?
|
||||
is_a?(TimeTrackable)
|
||||
end
|
||||
|
||||
def supports_severity?
|
||||
|
|
|
@ -9,7 +9,7 @@ module ReactiveCaching
|
|||
ExceededReactiveCacheLimit = Class.new(StandardError)
|
||||
|
||||
WORK_TYPE = {
|
||||
default: ReactiveCachingWorker,
|
||||
no_dependency: ReactiveCachingWorker,
|
||||
external_dependency: ExternalServiceReactiveCachingWorker
|
||||
}.freeze
|
||||
|
||||
|
@ -30,7 +30,6 @@ module ReactiveCaching
|
|||
self.reactive_cache_refresh_interval = 1.minute
|
||||
self.reactive_cache_lifetime = 10.minutes
|
||||
self.reactive_cache_hard_limit = nil # this value should be set in megabytes. E.g: 1.megabyte
|
||||
self.reactive_cache_work_type = :default
|
||||
self.reactive_cache_worker_finder = ->(id, *_args) do
|
||||
find_by(primary_key => id)
|
||||
end
|
||||
|
|
|
@ -8,5 +8,6 @@ module ReactiveService
|
|||
|
||||
# Default cache key: class name + project_id
|
||||
self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,6 +31,7 @@ class MergeRequest < ApplicationRecord
|
|||
self.reactive_cache_key = ->(model) { [model.project.id, model.iid] }
|
||||
self.reactive_cache_refresh_interval = 10.minutes
|
||||
self.reactive_cache_lifetime = 10.minutes
|
||||
self.reactive_cache_work_type = :no_dependency
|
||||
|
||||
SORTING_PREFERENCE_FIELD = :merge_requests_sort
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ module AlertManagement
|
|||
|
||||
MARKDOWN_LINE_BREAK = " \n"
|
||||
HORIZONTAL_LINE = "\n\n---\n\n"
|
||||
INCIDENT_LABEL_NAME = ::IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES[:title]
|
||||
|
||||
delegate :metrics_dashboard_url, :runbook, to: :parsed_payload
|
||||
|
||||
|
@ -38,6 +39,30 @@ module AlertManagement
|
|||
Gitlab::Utils::InlineHash.merge_keys(payload)
|
||||
end
|
||||
|
||||
def show_incident_issues_link?
|
||||
project.incident_management_setting&.create_issue?
|
||||
end
|
||||
|
||||
def show_performance_dashboard_link?
|
||||
prometheus_alert.present?
|
||||
end
|
||||
|
||||
def incident_issues_link
|
||||
project_issues_url(project, label_name: INCIDENT_LABEL_NAME)
|
||||
end
|
||||
|
||||
def performance_dashboard_link
|
||||
if environment
|
||||
metrics_project_environment_url(project, environment)
|
||||
else
|
||||
metrics_project_environments_url(project)
|
||||
end
|
||||
end
|
||||
|
||||
def email_title
|
||||
[environment&.name, query_title].compact.join(': ')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :alert, :project
|
||||
|
@ -80,5 +105,11 @@ module AlertManagement
|
|||
def host_links
|
||||
hosts.join(' ')
|
||||
end
|
||||
|
||||
def query_title
|
||||
return title unless prometheus_alert
|
||||
|
||||
"#{prometheus_alert.title} #{prometheus_alert.computed_operator} #{prometheus_alert.threshold} for 5 minutes"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,179 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
module Prometheus
|
||||
class AlertPresenter < Gitlab::View::Presenter::Delegated
|
||||
GENERIC_ALERT_SUMMARY_ANNOTATIONS = %w(monitoring_tool service hosts).freeze
|
||||
MARKDOWN_LINE_BREAK = " \n".freeze
|
||||
INCIDENT_LABEL_NAME = ::IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES[:title].freeze
|
||||
METRIC_TIME_WINDOW = 30.minutes
|
||||
|
||||
def full_title
|
||||
[environment_name, alert_title].compact.join(': ')
|
||||
end
|
||||
|
||||
def project_full_path
|
||||
project.full_path
|
||||
end
|
||||
|
||||
def metric_query
|
||||
gitlab_alert&.full_query
|
||||
end
|
||||
|
||||
def environment_name
|
||||
environment&.name
|
||||
end
|
||||
|
||||
def performance_dashboard_link
|
||||
if environment
|
||||
metrics_project_environment_url(project, environment)
|
||||
else
|
||||
metrics_project_environments_url(project)
|
||||
end
|
||||
end
|
||||
|
||||
def show_performance_dashboard_link?
|
||||
gitlab_alert.present?
|
||||
end
|
||||
|
||||
def show_incident_issues_link?
|
||||
project.incident_management_setting&.create_issue?
|
||||
end
|
||||
|
||||
def incident_issues_link
|
||||
project_issues_url(project, label_name: INCIDENT_LABEL_NAME)
|
||||
end
|
||||
|
||||
def start_time
|
||||
starts_at&.strftime('%d %B %Y, %-l:%M%p (%Z)')
|
||||
end
|
||||
|
||||
def issue_summary_markdown
|
||||
<<~MARKDOWN.chomp
|
||||
#{metadata_list}
|
||||
#{metric_embed_for_alert}
|
||||
MARKDOWN
|
||||
end
|
||||
|
||||
def metric_embed_for_alert
|
||||
"\n[](#{metrics_dashboard_url})" if metrics_dashboard_url
|
||||
end
|
||||
|
||||
def metrics_dashboard_url
|
||||
strong_memoize(:metrics_dashboard_url) do
|
||||
embed_url_for_gitlab_alert || embed_url_for_self_managed_alert
|
||||
end
|
||||
end
|
||||
|
||||
def details_url
|
||||
return unless am_alert
|
||||
|
||||
::Gitlab::Routing.url_helpers.details_project_alert_management_url(
|
||||
project,
|
||||
am_alert.iid
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def alert_title
|
||||
query_title || title
|
||||
end
|
||||
|
||||
def query_title
|
||||
return unless gitlab_alert
|
||||
|
||||
"#{gitlab_alert.title} #{gitlab_alert.computed_operator} #{gitlab_alert.threshold} for 5 minutes"
|
||||
end
|
||||
|
||||
def metadata_list
|
||||
metadata = []
|
||||
|
||||
metadata << list_item('Start time', start_time) if start_time
|
||||
metadata << list_item('full_query', backtick(full_query)) if full_query
|
||||
metadata << list_item(service.label.humanize, service.value) if service
|
||||
metadata << list_item(monitoring_tool.label.humanize, monitoring_tool.value) if monitoring_tool
|
||||
metadata << list_item(hosts.label.humanize, host_links) if hosts
|
||||
metadata << list_item('GitLab alert', details_url) if details_url
|
||||
|
||||
metadata.join(MARKDOWN_LINE_BREAK)
|
||||
end
|
||||
|
||||
def details
|
||||
Gitlab::Utils::InlineHash.merge_keys(payload)
|
||||
end
|
||||
|
||||
def list_item(key, value)
|
||||
"**#{key}:** #{value}".strip
|
||||
end
|
||||
|
||||
def backtick(value)
|
||||
"`#{value}`"
|
||||
end
|
||||
|
||||
GENERIC_ALERT_SUMMARY_ANNOTATIONS.each do |annotation_name|
|
||||
define_method(annotation_name) do
|
||||
annotations.find { |a| a.label == annotation_name }
|
||||
end
|
||||
end
|
||||
|
||||
def host_links
|
||||
Array(hosts.value).join(' ')
|
||||
end
|
||||
|
||||
def embed_url_for_gitlab_alert
|
||||
return unless gitlab_alert
|
||||
|
||||
metrics_dashboard_project_prometheus_alert_url(
|
||||
project,
|
||||
gitlab_alert.prometheus_metric_id,
|
||||
environment_id: environment.id,
|
||||
embedded: true,
|
||||
**alert_embed_window_params(embed_time)
|
||||
)
|
||||
end
|
||||
|
||||
def embed_url_for_self_managed_alert
|
||||
return unless environment && full_query && title
|
||||
|
||||
metrics_dashboard_project_environment_url(
|
||||
project,
|
||||
environment,
|
||||
embed_json: dashboard_for_self_managed_alert.to_json,
|
||||
embedded: true,
|
||||
**alert_embed_window_params(embed_time)
|
||||
)
|
||||
end
|
||||
|
||||
def embed_time
|
||||
starts_at || Time.current
|
||||
end
|
||||
|
||||
def alert_embed_window_params(time)
|
||||
{
|
||||
start: format_embed_timestamp(time - METRIC_TIME_WINDOW),
|
||||
end: format_embed_timestamp(time + METRIC_TIME_WINDOW)
|
||||
}
|
||||
end
|
||||
|
||||
def format_embed_timestamp(time)
|
||||
time.utc.strftime('%FT%TZ')
|
||||
end
|
||||
|
||||
def dashboard_for_self_managed_alert
|
||||
{
|
||||
panel_groups: [{
|
||||
panels: [{
|
||||
type: 'area-chart',
|
||||
title: title,
|
||||
y_label: y_label,
|
||||
metrics: [{
|
||||
query_range: full_query
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -47,7 +47,7 @@ module AlertManagement
|
|||
def create_alert_management_alert
|
||||
if alert.save
|
||||
alert.execute_services
|
||||
SystemNoteService.create_new_alert(alert, Gitlab::AlertManagement::AlertParams::MONITORING_TOOLS[:prometheus])
|
||||
SystemNoteService.create_new_alert(alert, Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus])
|
||||
return
|
||||
end
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ module PodLogs
|
|||
CACHE_KEY_GET_POD_LOG = 'get_pod_log'
|
||||
K8S_NAME_MAX_LENGTH = 253
|
||||
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
|
||||
def id
|
||||
cluster.id
|
||||
end
|
||||
|
|
|
@ -11,7 +11,6 @@ module PodLogs
|
|||
:pod_logs,
|
||||
:filter_return_keys
|
||||
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
self.reactive_cache_worker_finder = ->(id, _cache_key, namespace, params) { new(::Clusters::Cluster.find(id), namespace, params: params) }
|
||||
|
||||
private
|
||||
|
|
|
@ -17,7 +17,6 @@ module PodLogs
|
|||
:split_logs,
|
||||
:filter_return_keys
|
||||
|
||||
self.reactive_cache_work_type = :external_dependency
|
||||
self.reactive_cache_worker_finder = ->(id, _cache_key, namespace, params) { new(::Clusters::Cluster.find(id), namespace, params: params) }
|
||||
|
||||
private
|
||||
|
|
|
@ -7,43 +7,34 @@ module Projects
|
|||
include ::IncidentManagement::Settings
|
||||
|
||||
def execute(token)
|
||||
return bad_request unless valid_payload_size?
|
||||
return forbidden unless alerts_service_activated?
|
||||
return unauthorized unless valid_token?(token)
|
||||
|
||||
alert = process_alert
|
||||
process_alert
|
||||
return bad_request unless alert.persisted?
|
||||
|
||||
process_incident_issues(alert) if process_issues?
|
||||
process_incident_issues if process_issues?
|
||||
send_alert_email if send_email?
|
||||
|
||||
ServiceResponse.success
|
||||
rescue Gitlab::Alerting::NotificationPayloadParser::BadPayloadError
|
||||
bad_request
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
delegate :alerts_service, :alerts_service_activated?, to: :project
|
||||
|
||||
def am_alert_params
|
||||
strong_memoize(:am_alert_params) do
|
||||
Gitlab::AlertManagement::AlertParams.from_generic_alert(project: project, payload: params.to_h)
|
||||
end
|
||||
end
|
||||
|
||||
def process_alert
|
||||
existing_alert = find_alert_by_fingerprint(am_alert_params[:fingerprint])
|
||||
|
||||
if existing_alert
|
||||
process_existing_alert(existing_alert)
|
||||
if alert.persisted?
|
||||
process_existing_alert
|
||||
else
|
||||
create_alert
|
||||
end
|
||||
end
|
||||
|
||||
def process_existing_alert(alert)
|
||||
if am_alert_params[:ended_at].present?
|
||||
process_resolved_alert(alert)
|
||||
def process_existing_alert
|
||||
if incoming_payload.ends_at.present?
|
||||
process_resolved_alert
|
||||
else
|
||||
alert.register_new_event!
|
||||
end
|
||||
|
@ -51,10 +42,10 @@ module Projects
|
|||
alert
|
||||
end
|
||||
|
||||
def process_resolved_alert(alert)
|
||||
def process_resolved_alert
|
||||
return unless auto_close_incident?
|
||||
|
||||
if alert.resolve(am_alert_params[:ended_at])
|
||||
if alert.resolve(incoming_payload.ends_at)
|
||||
close_issue(alert.issue)
|
||||
end
|
||||
|
||||
|
@ -72,20 +63,13 @@ module Projects
|
|||
end
|
||||
|
||||
def create_alert
|
||||
alert = AlertManagement::Alert.create(am_alert_params.except(:ended_at))
|
||||
alert.execute_services if alert.persisted?
|
||||
return unless alert.save
|
||||
|
||||
alert.execute_services
|
||||
SystemNoteService.create_new_alert(alert, 'Generic Alert Endpoint')
|
||||
|
||||
alert
|
||||
end
|
||||
|
||||
def find_alert_by_fingerprint(fingerprint)
|
||||
return unless fingerprint
|
||||
|
||||
AlertManagement::Alert.not_resolved.for_fingerprint(project, fingerprint).first
|
||||
end
|
||||
|
||||
def process_incident_issues(alert)
|
||||
def process_incident_issues
|
||||
return if alert.issue
|
||||
|
||||
::IncidentManagement::ProcessAlertWorker.perform_async(nil, nil, alert.id)
|
||||
|
@ -94,11 +78,33 @@ module Projects
|
|||
def send_alert_email
|
||||
notification_service
|
||||
.async
|
||||
.prometheus_alerts_fired(project, [parsed_payload])
|
||||
.prometheus_alerts_fired(project, [alert.attributes])
|
||||
end
|
||||
|
||||
def parsed_payload
|
||||
Gitlab::Alerting::NotificationPayloadParser.call(params.to_h, project)
|
||||
def alert
|
||||
strong_memoize(:alert) do
|
||||
existing_alert || new_alert
|
||||
end
|
||||
end
|
||||
|
||||
def existing_alert
|
||||
return unless incoming_payload.gitlab_fingerprint
|
||||
|
||||
AlertManagement::Alert.not_resolved.for_fingerprint(project, incoming_payload.gitlab_fingerprint).first
|
||||
end
|
||||
|
||||
def new_alert
|
||||
AlertManagement::Alert.new(**incoming_payload.alert_params, ended_at: nil)
|
||||
end
|
||||
|
||||
def incoming_payload
|
||||
strong_memoize(:incoming_payload) do
|
||||
Gitlab::AlertManagement::Payload.parse(project, params.to_h)
|
||||
end
|
||||
end
|
||||
|
||||
def valid_payload_size?
|
||||
Gitlab::Utils::DeepSize.new(params).valid?
|
||||
end
|
||||
|
||||
def valid_token?(token)
|
||||
|
|
|
@ -125,7 +125,7 @@ module Projects
|
|||
|
||||
notification_service
|
||||
.async
|
||||
.prometheus_alerts_fired(project, firings)
|
||||
.prometheus_alerts_fired(project, alerts_attributes)
|
||||
end
|
||||
|
||||
def process_prometheus_alerts
|
||||
|
@ -136,6 +136,18 @@ module Projects
|
|||
end
|
||||
end
|
||||
|
||||
def alerts_attributes
|
||||
firings.map do |payload|
|
||||
alert_params = Gitlab::AlertManagement::Payload.parse(
|
||||
project,
|
||||
payload,
|
||||
monitoring_tool: Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus]
|
||||
).alert_params
|
||||
|
||||
AlertManagement::Alert.new(alert_params).attributes
|
||||
end
|
||||
end
|
||||
|
||||
def bad_request
|
||||
ServiceResponse.error(message: 'Bad Request', http_status: :bad_request)
|
||||
end
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
%p
|
||||
= _('An alert has been triggered in %{project_path}.') % { project_path: @alert.project_full_path }
|
||||
= _('An alert has been triggered in %{project_path}.') % { project_path: @alert.project.full_path }
|
||||
|
||||
- if description = @alert.description
|
||||
%p
|
||||
= _('Description:')
|
||||
= description
|
||||
|
||||
- if env_name = @alert.environment_name
|
||||
- if env_name = @alert.environment&.name
|
||||
%p
|
||||
= _('Environment:')
|
||||
= env_name
|
||||
|
||||
- if metric_query = @alert.metric_query
|
||||
- if metric_query = @alert.prometheus_alert&.full_query
|
||||
%p
|
||||
= _('Metric:')
|
||||
|
||||
|
@ -25,4 +25,3 @@
|
|||
- if @alert.show_performance_dashboard_link?
|
||||
%p
|
||||
= link_to(_('View performance dashboard.'), @alert.performance_dashboard_link)
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<%= _('An alert has been triggered in %{project_path}.') % { project_path: @alert.project_full_path } %>.
|
||||
<%= _('An alert has been triggered in %{project_path}.') % { project_path: @alert.project.full_path } %>.
|
||||
|
||||
<% if description = @alert.description %>
|
||||
<%= _('Description:') %> <%= description %>
|
||||
<% end %>
|
||||
|
||||
<% if env_name = @alert.environment_name %>
|
||||
<% if env_name = @alert.environment&.name %>
|
||||
<%= _('Environment:') %> <%= env_name %>
|
||||
<% end %>
|
||||
|
||||
<% if metric_query = @alert.metric_query %>
|
||||
<% if metric_query = @alert.prometheus_alert&.full_query %>
|
||||
<%= _('Metric:') %> <%= metric_query %>
|
||||
<% end %>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
- if diff_file.submodule?
|
||||
- blob = diff_file.blob
|
||||
%span
|
||||
= icon('archive fw')
|
||||
= sprite_icon('archive')
|
||||
|
||||
%strong.file-title-name
|
||||
= submodule_link(blob, diff_file.content_sha, diff_file.repository)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Breadcrumb like UI for project path in packages list
|
||||
merge_request: 42684
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow time tracking in incidents
|
||||
merge_request: 42965
|
||||
author:
|
||||
type: changed
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: boards_with_swimlanes
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group: group::project management
|
||||
type: development
|
||||
default_enabled: false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: bulk_update_health_status
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group: group::portfolio management
|
||||
type: development
|
||||
default_enabled: true
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: burnup_charts
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group: group::project management
|
||||
type: development
|
||||
default_enabled: false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: graphql_board_lists
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group: group::project management
|
||||
type: development
|
||||
default_enabled: false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: multi_select_board
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group: group::project management
|
||||
type: development
|
||||
default_enabled: true
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: tribute_autocomplete
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32671
|
||||
rollout_issue_url:
|
||||
group: group::project management
|
||||
type: development
|
||||
default_enabled: false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: vue_issuable_sidebar
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18199
|
||||
rollout_issue_url:
|
||||
group: group::project management
|
||||
type: development
|
||||
default_enabled: false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: vue_issuables_list
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/15091
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/208093
|
||||
group: group::project management
|
||||
type: development
|
||||
default_enabled: false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: vue_sidebar_labels
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41561
|
||||
rollout_issue_url:
|
||||
group: group::project management
|
||||
type: development
|
||||
default_enabled: false
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
TELEMETRY_CHANGED_FILES_MESSAGE = <<~MSG
|
||||
For the following files, a review from the [Data team and Telemetry team](https://gitlab.com/groups/gitlab-org/growth/telemetry/engineers/-/group_members?with_inherited_permissions=exclude) is recommended
|
||||
Please check the ~telemetry [guide](https://docs.gitlab.com/ee/development/telemetry/usage_ping.html) and reach out to @gitlab-org/growth/telemetry/engineers group for a review.
|
||||
Please check the ~telemetry [guide](https://docs.gitlab.com/ee/development/telemetry/usage_ping.html) and reach out to %<telemetry_engineers_group>s group for a review.
|
||||
|
||||
%<changed_files>s
|
||||
|
||||
|
@ -13,6 +13,8 @@ UPDATE_METRICS_DEFINITIONS_MESSAGE = <<~MSG
|
|||
|
||||
MSG
|
||||
|
||||
TELEMETRY_ENGINEERS_GROUP = '@gitlab-org/growth/telemetry/engineers'
|
||||
|
||||
tracking_files = [
|
||||
'lib/gitlab/tracking.rb',
|
||||
'spec/lib/gitlab/tracking_spec.rb',
|
||||
|
@ -28,7 +30,14 @@ snowplow_events_changed_files = git.modified_files & tracking_files
|
|||
changed_files = (usage_data_changed_files + snowplow_events_changed_files)
|
||||
|
||||
if changed_files.any?
|
||||
warn format(TELEMETRY_CHANGED_FILES_MESSAGE, changed_files: helper.markdown_list(changed_files))
|
||||
|
||||
mention = if helper.draft_mr?
|
||||
"`#{TELEMETRY_ENGINEERS_GROUP}`"
|
||||
else
|
||||
TELEMETRY_ENGINEERS_GROUP
|
||||
end
|
||||
|
||||
warn format(TELEMETRY_CHANGED_FILES_MESSAGE, changed_files: helper.markdown_list(changed_files), telemetry_engineers_group: mention)
|
||||
warn format(UPDATE_METRICS_DEFINITIONS_MESSAGE) unless helper.changed_files(/usage_ping\.md/).any?
|
||||
|
||||
telemetry_labels = ['telemetry']
|
||||
|
|
|
@ -85,9 +85,7 @@ The ReactiveCaching concern can be used in models as well as `project_services`
|
|||
|
||||
1. Implement the `calculate_reactive_cache` method in your model/service.
|
||||
1. Call `with_reactive_cache` in your model/service where the cached value is needed.
|
||||
1. If the `calculate_reactive_cache` method above submits requests to external services
|
||||
(e.g. Prometheus, K8s), make sure to change the
|
||||
[`reactive_cache_work_type` accordingly](#selfreactive_cache_work_type).
|
||||
1. Set the [`reactive_cache_work_type` accordingly](#selfreactive_cache_work_type).
|
||||
|
||||
### In controllers
|
||||
|
||||
|
@ -252,7 +250,7 @@ self.reactive_cache_hard_limit = 5.megabytes
|
|||
- This is the type of work performed by the `calculate_reactive_cache` method. Based on this attribute,
|
||||
it's able to pick the right worker to process the caching job. Make sure to
|
||||
set it as `:external_dependency` if the work performs any external request
|
||||
(e.g. Kubernetes, Sentry).
|
||||
(e.g. Kubernetes, Sentry); otherwise set it to `:no_dependency`.
|
||||
|
||||
#### `self.reactive_cache_worker_finder`
|
||||
|
||||
|
|
|
@ -157,6 +157,11 @@ PREFIX=/usr sudo -E make install
|
|||
|
||||
After installation, be sure to [enable Elasticsearch](#enabling-elasticsearch).
|
||||
|
||||
NOTE: **Note:**
|
||||
If you see an error such as `Permission denied - /home/git/gitlab-elasticsearch-indexer/` while indexing, you
|
||||
may need to set the `production -> elasticsearch -> indexer_path` setting in your `gitlab.yml` file to
|
||||
`/usr/local/bin/gitlab-elasticsearch-indexer`, which is where the binary is installed.
|
||||
|
||||
## Enabling Elasticsearch
|
||||
|
||||
NOTE: **Note:**
|
||||
|
@ -725,13 +730,11 @@ Here are some common pitfalls and how to overcome them:
|
|||
newer versions of Elasticsearch. When indexing changes are made, it may
|
||||
be necessary for you to [reindex](#zero-downtime-reindexing) after updating GitLab.
|
||||
|
||||
- **I indexed all the repositories but I can't find anything**
|
||||
- **I indexed all the repositories but I can't get any hits for my search term in the UI**
|
||||
|
||||
Make sure you indexed all the database data [as stated above](#enabling-elasticsearch).
|
||||
|
||||
Beyond that, check via the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) to see if the data shows up on the Elasticsearch side.
|
||||
|
||||
If it shows up via the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html), check that it shows up via the rails console (`sudo gitlab-rails console`):
|
||||
If there aren't any results (hits) in the UI search, check if you are seeing the same results via the rails console (`sudo gitlab-rails console`):
|
||||
|
||||
```ruby
|
||||
u = User.find_by_username('your-username')
|
||||
|
@ -739,6 +742,16 @@ Here are some common pitfalls and how to overcome them:
|
|||
pp s.search_objects.to_a
|
||||
```
|
||||
|
||||
Beyond that, check via the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) to see if the data shows up on the Elasticsearch side:
|
||||
|
||||
```shell
|
||||
curl --request GET <elasticsearch_server_ip>:9200/gitlab-production/_search?q=<search_term>
|
||||
```
|
||||
|
||||
More [complex Elasticsearch API calls](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html) are also possible.
|
||||
|
||||
It is important to understand at which level the problem is manifesting (UI, Rails code, Elasticsearch side) to be able to [troubleshoot further](../administration/troubleshooting/elasticsearch.md#search-results-workflow).
|
||||
|
||||
NOTE: **Note:**
|
||||
The above instructions are not to be used for scenarios that only index a [subset of namespaces](#limiting-namespaces-and-projects).
|
||||
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module AlertManagement
|
||||
class AlertParams
|
||||
MONITORING_TOOLS = {
|
||||
prometheus: 'Prometheus'
|
||||
}.freeze
|
||||
|
||||
def self.from_generic_alert(project:, payload:)
|
||||
parsed_payload = Gitlab::Alerting::NotificationPayloadParser.call(payload, project).with_indifferent_access
|
||||
annotations = parsed_payload[:annotations]
|
||||
|
||||
{
|
||||
project_id: project.id,
|
||||
title: annotations[:title],
|
||||
description: annotations[:description],
|
||||
monitoring_tool: annotations[:monitoring_tool],
|
||||
service: annotations[:service],
|
||||
hosts: Array(annotations[:hosts]),
|
||||
payload: payload,
|
||||
started_at: parsed_payload['startsAt'],
|
||||
ended_at: parsed_payload['endsAt'],
|
||||
severity: annotations[:severity],
|
||||
fingerprint: annotations[:fingerprint],
|
||||
environment: annotations[:environment]
|
||||
}
|
||||
end
|
||||
|
||||
def self.from_prometheus_alert(project:, parsed_alert:)
|
||||
{
|
||||
project_id: project.id,
|
||||
title: parsed_alert.title,
|
||||
description: parsed_alert.description,
|
||||
monitoring_tool: MONITORING_TOOLS[:prometheus],
|
||||
payload: parsed_alert.payload,
|
||||
started_at: parsed_alert.starts_at,
|
||||
ended_at: parsed_alert.ends_at,
|
||||
fingerprint: parsed_alert.gitlab_fingerprint,
|
||||
environment: parsed_alert.environment,
|
||||
prometheus_alert: parsed_alert.gitlab_alert
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,6 +8,8 @@ module Gitlab
|
|||
DEFAULT_TITLE = 'New: Incident'
|
||||
DEFAULT_SEVERITY = 'critical'
|
||||
|
||||
attribute :description, paths: 'description'
|
||||
attribute :ends_at, paths: 'end_time', type: :time
|
||||
attribute :environment_name, paths: 'gitlab_environment_name'
|
||||
attribute :hosts, paths: 'hosts'
|
||||
attribute :monitoring_tool, paths: 'monitoring_tool'
|
||||
|
@ -23,3 +25,5 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::AlertManagement::Payload::Generic.prepend_if_ee('EE::Gitlab::AlertManagement::Payload::Generic')
|
||||
|
|
|
@ -1,215 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Alerting
|
||||
class Alert
|
||||
include ActiveModel::Model
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include Presentable
|
||||
|
||||
attr_accessor :project, :payload, :am_alert
|
||||
|
||||
def self.for_alert_management_alert(project:, alert:)
|
||||
params = if alert.prometheus?
|
||||
alert.payload
|
||||
else
|
||||
Gitlab::Alerting::NotificationPayloadParser.call(alert.payload.to_h, alert.project)
|
||||
end
|
||||
|
||||
self.new(project: project, payload: params, am_alert: alert)
|
||||
end
|
||||
|
||||
def gitlab_alert
|
||||
strong_memoize(:gitlab_alert) do
|
||||
parse_gitlab_alert_from_payload
|
||||
end
|
||||
end
|
||||
|
||||
def metric_id
|
||||
strong_memoize(:metric_id) do
|
||||
payload&.dig('labels', 'gitlab_alert_id')
|
||||
end
|
||||
end
|
||||
|
||||
def gitlab_prometheus_alert_id
|
||||
strong_memoize(:gitlab_prometheus_alert_id) do
|
||||
payload&.dig('labels', 'gitlab_prometheus_alert_id')
|
||||
end
|
||||
end
|
||||
|
||||
def title
|
||||
strong_memoize(:title) do
|
||||
gitlab_alert&.title || parse_title_from_payload
|
||||
end
|
||||
end
|
||||
|
||||
def description
|
||||
strong_memoize(:description) do
|
||||
parse_description_from_payload
|
||||
end
|
||||
end
|
||||
|
||||
def environment
|
||||
strong_memoize(:environment) do
|
||||
gitlab_alert&.environment || parse_environment_from_payload
|
||||
end
|
||||
end
|
||||
|
||||
def annotations
|
||||
strong_memoize(:annotations) do
|
||||
parse_annotations_from_payload || []
|
||||
end
|
||||
end
|
||||
|
||||
def starts_at
|
||||
strong_memoize(:starts_at) do
|
||||
parse_datetime_from_payload('startsAt')
|
||||
end
|
||||
end
|
||||
|
||||
def starts_at_raw
|
||||
strong_memoize(:starts_at_raw) do
|
||||
payload&.dig('startsAt')
|
||||
end
|
||||
end
|
||||
|
||||
def ends_at
|
||||
strong_memoize(:ends_at) do
|
||||
parse_datetime_from_payload('endsAt')
|
||||
end
|
||||
end
|
||||
|
||||
def full_query
|
||||
strong_memoize(:full_query) do
|
||||
gitlab_alert&.full_query || parse_expr_from_payload
|
||||
end
|
||||
end
|
||||
|
||||
def y_label
|
||||
strong_memoize(:y_label) do
|
||||
parse_y_label_from_payload || title
|
||||
end
|
||||
end
|
||||
|
||||
def alert_markdown
|
||||
strong_memoize(:alert_markdown) do
|
||||
parse_alert_markdown_from_payload
|
||||
end
|
||||
end
|
||||
|
||||
def status
|
||||
strong_memoize(:status) do
|
||||
payload&.dig('status')
|
||||
end
|
||||
end
|
||||
|
||||
def firing?
|
||||
status == 'firing'
|
||||
end
|
||||
|
||||
def resolved?
|
||||
status == 'resolved'
|
||||
end
|
||||
|
||||
def gitlab_managed?
|
||||
metric_id.present?
|
||||
end
|
||||
|
||||
def gitlab_fingerprint
|
||||
Gitlab::AlertManagement::Fingerprint.generate(plain_gitlab_fingerprint)
|
||||
end
|
||||
|
||||
def valid?
|
||||
payload.respond_to?(:dig) && project && title && starts_at
|
||||
end
|
||||
|
||||
def present
|
||||
super(presenter_class: Projects::Prometheus::AlertPresenter)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def plain_gitlab_fingerprint
|
||||
if gitlab_managed?
|
||||
[metric_id, starts_at_raw].join('/')
|
||||
else # self managed
|
||||
[starts_at_raw, title, full_query].join('/')
|
||||
end
|
||||
end
|
||||
|
||||
def parse_environment_from_payload
|
||||
environment_name = payload&.dig('labels', 'gitlab_environment_name')
|
||||
|
||||
return unless environment_name
|
||||
|
||||
EnvironmentsFinder.new(project, nil, { name: environment_name })
|
||||
.find
|
||||
&.first
|
||||
end
|
||||
|
||||
def parse_gitlab_alert_from_payload
|
||||
alerts_found = matching_gitlab_alerts
|
||||
|
||||
return if alerts_found.blank? || alerts_found.size > 1
|
||||
|
||||
alerts_found.first
|
||||
end
|
||||
|
||||
def matching_gitlab_alerts
|
||||
return unless metric_id || gitlab_prometheus_alert_id
|
||||
|
||||
Projects::Prometheus::AlertsFinder
|
||||
.new(project: project, metric: metric_id, id: gitlab_prometheus_alert_id)
|
||||
.execute
|
||||
end
|
||||
|
||||
def parse_title_from_payload
|
||||
payload&.dig('annotations', 'title') ||
|
||||
payload&.dig('annotations', 'summary') ||
|
||||
payload&.dig('labels', 'alertname')
|
||||
end
|
||||
|
||||
def parse_description_from_payload
|
||||
payload&.dig('annotations', 'description')
|
||||
end
|
||||
|
||||
def parse_annotations_from_payload
|
||||
payload&.dig('annotations')&.map do |label, value|
|
||||
Alerting::AlertAnnotation.new(label: label, value: value)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_datetime_from_payload(field)
|
||||
value = payload&.dig(field)
|
||||
return unless value
|
||||
|
||||
# value is a rfc3339 timestamp
|
||||
# Timestamps from Prometheus and Alertmanager are UTC RFC3339 timestamps like: '2018-03-12T09:06:00Z' (Z represents 0 offset or UTC)
|
||||
# .utc sets the datetime zone to `UTC`
|
||||
Time.rfc3339(value).utc
|
||||
rescue ArgumentError
|
||||
end
|
||||
|
||||
# Parses `g0.expr` from `generatorURL`.
|
||||
#
|
||||
# Example: http://localhost:9090/graph?g0.expr=vector%281%29&g0.tab=1
|
||||
def parse_expr_from_payload
|
||||
url = payload&.dig('generatorURL')
|
||||
return unless url
|
||||
|
||||
uri = URI(url)
|
||||
|
||||
Rack::Utils.parse_query(uri.query).fetch('g0.expr')
|
||||
rescue URI::InvalidURIError, KeyError
|
||||
end
|
||||
|
||||
def parse_alert_markdown_from_payload
|
||||
payload&.dig('annotations', 'gitlab_incident_markdown')
|
||||
end
|
||||
|
||||
def parse_y_label_from_payload
|
||||
payload&.dig('annotations', 'gitlab_y_label')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -214,6 +214,12 @@ module Gitlab
|
|||
title.gsub(DRAFT_REGEX, '').gsub(/`/, '\\\`')
|
||||
end
|
||||
|
||||
def draft_mr?
|
||||
return false unless gitlab_helper
|
||||
|
||||
DRAFT_REGEX.match?(gitlab_helper.mr_json['title'])
|
||||
end
|
||||
|
||||
def security_mr?
|
||||
return false unless gitlab_helper
|
||||
|
||||
|
|
|
@ -145,11 +145,11 @@
|
|||
"url-loader": "^3.0.0",
|
||||
"uuid": "8.1.0",
|
||||
"visibilityjs": "^1.2.4",
|
||||
"vue": "^2.6.10",
|
||||
"vue": "^2.6.12",
|
||||
"vue-apollo": "^3.0.3",
|
||||
"vue-loader": "^15.9.0",
|
||||
"vue-loader": "^15.9.3",
|
||||
"vue-router": "^3.4.3",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"vue-virtual-scroll-list": "^1.4.4",
|
||||
"vuedraggable": "^2.23.0",
|
||||
"vuex": "^3.5.1",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
export SETUP_DB=${SETUP_DB:-true}
|
||||
export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
|
||||
export BUNDLE_INSTALL_FLAGS=${BUNDLE_INSTALL_FLAGS:-"--without=production --without=development --jobs=$(nproc) --path=vendor --retry=3 --quiet"}
|
||||
export BUNDLE_INSTALL_FLAGS=${BUNDLE_INSTALL_FLAGS:-"--without=production development --jobs=$(nproc) --path=vendor --retry=3 --quiet"}
|
||||
|
||||
if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
|
||||
bundle --version
|
||||
|
@ -14,10 +14,6 @@ if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
|
|||
run_timed_command "bundle pristine pg"
|
||||
fi
|
||||
|
||||
# Only install knapsack after bundle install! Otherwise oddly some native
|
||||
# gems could not be found under some circumstance. No idea why, hours wasted.
|
||||
run_timed_command "gem install knapsack --no-document"
|
||||
|
||||
cp config/gitlab.yml.example config/gitlab.yml
|
||||
sed -i 's/bin_path: \/usr\/bin\/git/bin_path: \/usr\/local\/bin\/git/' config/gitlab.yml
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ RSpec.describe Projects::Import::JiraController do
|
|||
def ensure_correct_config
|
||||
sign_in(user)
|
||||
project.add_maintainer(user)
|
||||
stub_feature_flags(jira_issue_import: true)
|
||||
stub_jira_service_test
|
||||
end
|
||||
|
||||
|
@ -77,7 +76,6 @@ RSpec.describe Projects::Import::JiraController do
|
|||
before do
|
||||
sign_in(user)
|
||||
project.add_maintainer(user)
|
||||
stub_feature_flags(jira_issue_import: true)
|
||||
end
|
||||
|
||||
context 'when Jira service is not enabled for the project' do
|
||||
|
|
|
@ -100,7 +100,7 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
trait :prometheus do
|
||||
monitoring_tool { Gitlab::AlertManagement::AlertParams::MONITORING_TOOLS[:prometheus] }
|
||||
monitoring_tool { Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus] }
|
||||
payload do
|
||||
{
|
||||
annotations: {
|
||||
|
@ -123,5 +123,17 @@ FactoryBot.define do
|
|||
with_description
|
||||
low
|
||||
end
|
||||
|
||||
trait :from_payload do
|
||||
after(:build) do |alert|
|
||||
alert_params = ::Gitlab::AlertManagement::Payload.parse(
|
||||
alert.project,
|
||||
alert.payload,
|
||||
monitoring_tool: alert.monitoring_tool
|
||||
).alert_params
|
||||
|
||||
alert.assign_attributes(alert_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :alerting_alert, class: 'Gitlab::Alerting::Alert' do
|
||||
project
|
||||
payload { {} }
|
||||
|
||||
transient do
|
||||
metric_id { nil }
|
||||
|
||||
after(:build) do |alert, evaluator|
|
||||
unless alert.payload.key?('startsAt')
|
||||
alert.payload['startsAt'] = Time.now.rfc3339
|
||||
end
|
||||
|
||||
if metric_id = evaluator.metric_id
|
||||
alert.payload['labels'] ||= {}
|
||||
alert.payload['labels']['gitlab_alert_id'] = metric_id.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
skip_create
|
||||
end
|
||||
end
|
|
@ -51,27 +51,6 @@ exports[`packages_list_row renders 1`] = `
|
|||
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-center"
|
||||
>
|
||||
<gl-icon-stub
|
||||
class="gl-ml-3 gl-mr-2 gl-min-w-0"
|
||||
name="review-list"
|
||||
size="16"
|
||||
/>
|
||||
|
||||
<gl-link-stub
|
||||
class="gl-text-body gl-min-w-0"
|
||||
data-testid="packages-row-project"
|
||||
href="/foo/bar/baz"
|
||||
>
|
||||
<gl-truncate-stub
|
||||
position="end"
|
||||
text="foo/bar/baz"
|
||||
/>
|
||||
</gl-link-stub>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="d-flex align-items-center"
|
||||
data-testid="package-type"
|
||||
|
@ -86,6 +65,10 @@ exports[`packages_list_row renders 1`] = `
|
|||
Maven
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<package-path-stub
|
||||
path="foo/bar/baz"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import PackagesListRow from '~/packages/shared/components/package_list_row.vue';
|
||||
import PackageTags from '~/packages/shared/components/package_tags.vue';
|
||||
import PackagePath from '~/packages/shared/components/package_path.vue';
|
||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
import { packageList } from '../../mock_data';
|
||||
|
||||
|
@ -11,7 +12,7 @@ describe('packages_list_row', () => {
|
|||
const [packageWithoutTags, packageWithTags] = packageList;
|
||||
|
||||
const findPackageTags = () => wrapper.find(PackageTags);
|
||||
const findProjectLink = () => wrapper.find('[data-testid="packages-row-project"]');
|
||||
const findPackagePath = () => wrapper.find(PackagePath);
|
||||
const findDeleteButton = () => wrapper.find('[data-testid="action-delete"]');
|
||||
const findPackageType = () => wrapper.find('[data-testid="package-type"]');
|
||||
|
||||
|
@ -63,8 +64,9 @@ describe('packages_list_row', () => {
|
|||
mountComponent({ isGroup: true });
|
||||
});
|
||||
|
||||
it('has project field', () => {
|
||||
expect(findProjectLink().exists()).toBe(true);
|
||||
it('has a package path component', () => {
|
||||
expect(findPackagePath().exists()).toBe(true);
|
||||
expect(findPackagePath().props()).toMatchObject({ path: 'foo/bar/baz' });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import PackagePath from '~/packages/shared/components/package_path.vue';
|
||||
|
||||
describe('PackagePath', () => {
|
||||
let wrapper;
|
||||
|
||||
const mountComponent = (propsData = { path: 'foo' }) => {
|
||||
wrapper = shallowMount(PackagePath, {
|
||||
propsData,
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const BASE_ICON = 'base-icon';
|
||||
const ROOT_LINK = 'root-link';
|
||||
const ROOT_CHEVRON = 'root-chevron';
|
||||
const ELLIPSIS_ICON = 'ellipsis-icon';
|
||||
const ELLIPSIS_CHEVRON = 'ellipsis-chevron';
|
||||
const LEAF_LINK = 'leaf-link';
|
||||
|
||||
const findItem = name => wrapper.find(`[data-testid="${name}"]`);
|
||||
const findTooltip = w => getBinding(w.element, 'gl-tooltip');
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe.each`
|
||||
path | rootUrl | shouldExist | shouldNotExist
|
||||
${'foo/bar'} | ${'/foo/bar'} | ${[]} | ${[ROOT_CHEVRON, ELLIPSIS_ICON, ELLIPSIS_CHEVRON, LEAF_LINK]}
|
||||
${'foo/bar/baz'} | ${'/foo/bar'} | ${[ROOT_CHEVRON, LEAF_LINK]} | ${[ELLIPSIS_ICON, ELLIPSIS_CHEVRON]}
|
||||
${'foo/bar/baz/baz2'} | ${'/foo/bar'} | ${[ROOT_CHEVRON, LEAF_LINK, ELLIPSIS_ICON, ELLIPSIS_CHEVRON]} | ${[]}
|
||||
${'foo/bar/baz/baz2/bar2'} | ${'/foo/bar'} | ${[ROOT_CHEVRON, LEAF_LINK, ELLIPSIS_ICON, ELLIPSIS_CHEVRON]} | ${[]}
|
||||
`('given path $path', ({ path, shouldExist, shouldNotExist, rootUrl }) => {
|
||||
const pathPieces = path.split('/').slice(1);
|
||||
const hasTooltip = shouldExist.includes(ELLIPSIS_ICON);
|
||||
|
||||
beforeEach(() => {
|
||||
mountComponent({ path });
|
||||
});
|
||||
|
||||
it('should have a base icon', () => {
|
||||
expect(findItem(BASE_ICON).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should have a root link', () => {
|
||||
const root = findItem(ROOT_LINK);
|
||||
expect(root.exists()).toBe(true);
|
||||
expect(root.attributes('href')).toBe(rootUrl);
|
||||
});
|
||||
|
||||
if (hasTooltip) {
|
||||
it('should have a tooltip', () => {
|
||||
const tooltip = findTooltip(findItem(ELLIPSIS_ICON));
|
||||
expect(tooltip).toBeDefined();
|
||||
expect(tooltip.value).toMatchObject({
|
||||
title: path,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldExist.length) {
|
||||
it.each(shouldExist)(`should have %s`, element => {
|
||||
expect(findItem(element).exists()).toBe(true);
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldNotExist.length) {
|
||||
it.each(shouldNotExist)(`should not have %s`, element => {
|
||||
expect(findItem(element).exists()).toBe(false);
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldExist.includes(LEAF_LINK)) {
|
||||
it('the last link should be the last piece of the path', () => {
|
||||
const leaf = findItem(LEAF_LINK);
|
||||
expect(leaf.attributes('href')).toBe(`/${path}`);
|
||||
expect(leaf.text()).toBe(pathPieces[pathPieces.length - 1]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,101 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::AlertManagement::AlertParams do
|
||||
let_it_be(:project) { create(:project, :repository, :private) }
|
||||
|
||||
describe '.from_generic_alert' do
|
||||
let(:started_at) { Time.current.change(usec: 0).rfc3339 }
|
||||
let(:default_payload) do
|
||||
{
|
||||
'title' => 'Alert title',
|
||||
'description' => 'Description',
|
||||
'monitoring_tool' => 'Monitoring tool name',
|
||||
'service' => 'Service',
|
||||
'hosts' => ['gitlab.com'],
|
||||
'start_time' => started_at,
|
||||
'some' => { 'extra' => { 'payload' => 'here' } }
|
||||
}
|
||||
end
|
||||
|
||||
let(:payload) { default_payload }
|
||||
|
||||
subject { described_class.from_generic_alert(project: project, payload: payload) }
|
||||
|
||||
it 'returns Alert compatible parameters' do
|
||||
is_expected.to eq(
|
||||
project_id: project.id,
|
||||
title: 'Alert title',
|
||||
description: 'Description',
|
||||
monitoring_tool: 'Monitoring tool name',
|
||||
service: 'Service',
|
||||
severity: 'critical',
|
||||
hosts: ['gitlab.com'],
|
||||
payload: payload,
|
||||
started_at: started_at,
|
||||
ended_at: nil,
|
||||
fingerprint: nil,
|
||||
environment: nil
|
||||
)
|
||||
end
|
||||
|
||||
context 'when severity given' do
|
||||
let(:payload) { default_payload.merge(severity: 'low') }
|
||||
|
||||
it 'returns Alert compatible parameters' do
|
||||
expect(subject[:severity]).to eq('low')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no hosts in the payload' do
|
||||
let(:payload) { {} }
|
||||
|
||||
it 'hosts param is an empty array' do
|
||||
expect(subject[:hosts]).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.from_prometheus_alert' do
|
||||
let(:payload) do
|
||||
{
|
||||
'status' => 'firing',
|
||||
'labels' => {
|
||||
'alertname' => 'GitalyFileServerDown',
|
||||
'channel' => 'gitaly',
|
||||
'pager' => 'pagerduty',
|
||||
'severity' => 's1'
|
||||
},
|
||||
'annotations' => {
|
||||
'description' => 'Alert description',
|
||||
'runbook' => 'troubleshooting/gitaly-down.md',
|
||||
'title' => 'Alert title'
|
||||
},
|
||||
'startsAt' => '2020-04-27T10:10:22.265949279Z',
|
||||
'endsAt' => '0001-01-01T00:00:00Z',
|
||||
'generatorURL' => 'http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1',
|
||||
'fingerprint' => 'b6ac4d42057c43c1'
|
||||
}
|
||||
end
|
||||
|
||||
let(:parsed_alert) { Gitlab::Alerting::Alert.new(project: project, payload: payload) }
|
||||
|
||||
subject { described_class.from_prometheus_alert(project: project, parsed_alert: parsed_alert) }
|
||||
|
||||
it 'returns Alert-compatible params' do
|
||||
is_expected.to eq(
|
||||
project_id: project.id,
|
||||
title: 'Alert title',
|
||||
description: 'Alert description',
|
||||
monitoring_tool: 'Prometheus',
|
||||
payload: payload,
|
||||
started_at: parsed_alert.starts_at,
|
||||
ended_at: parsed_alert.ends_at,
|
||||
fingerprint: parsed_alert.gitlab_fingerprint,
|
||||
environment: parsed_alert.environment,
|
||||
prometheus_alert: parsed_alert.gitlab_alert
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -86,4 +86,34 @@ RSpec.describe Gitlab::AlertManagement::Payload::Generic do
|
|||
|
||||
it_behaves_like 'parsable alert payload field', 'gitlab_environment_name'
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
subject { parsed_payload.description }
|
||||
|
||||
it_behaves_like 'parsable alert payload field', 'description'
|
||||
end
|
||||
|
||||
describe '#ends_at' do
|
||||
let(:current_time) { Time.current.change(usec: 0).utc }
|
||||
|
||||
subject { parsed_payload.ends_at }
|
||||
|
||||
around do |example|
|
||||
Timecop.freeze(current_time) { example.run }
|
||||
end
|
||||
|
||||
context 'without end_time' do
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context "with end_time" do
|
||||
let(:value) { 10.minutes.ago.change(usec: 0).utc }
|
||||
|
||||
before do
|
||||
raw_payload['end_time'] = value.to_s
|
||||
end
|
||||
|
||||
it { is_expected.to eq(value) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,299 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Alerting::Alert do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:alert) { build(:alerting_alert, project: project, payload: payload) }
|
||||
let(:payload) { {} }
|
||||
|
||||
shared_context 'gitlab alert' do
|
||||
let!(:gitlab_alert) { create(:prometheus_alert, project: project) }
|
||||
let(:gitlab_alert_id) { gitlab_alert.id }
|
||||
|
||||
before do
|
||||
payload['labels'] = {
|
||||
'gitlab_alert_id' => gitlab_alert.prometheus_metric_id.to_s,
|
||||
'gitlab_prometheus_alert_id' => gitlab_alert_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
shared_context 'full query' do
|
||||
before do
|
||||
payload['generatorURL'] = 'http://localhost:9090/graph?g0.expr=vector%281%29'
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'invalid alert' do
|
||||
it 'is invalid' do
|
||||
expect(alert).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'parse payload' do |*pairs|
|
||||
context 'without payload' do
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
pairs.each do |pair|
|
||||
context "with #{pair}" do
|
||||
let(:value) { 'some value' }
|
||||
|
||||
before do
|
||||
section, name = pair.split('/')
|
||||
payload[section] = { name => value }
|
||||
end
|
||||
|
||||
it { is_expected.to eq(value) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#gitlab_alert' do
|
||||
subject { alert.gitlab_alert }
|
||||
|
||||
context 'without payload' do
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'with gitlab alert' do
|
||||
include_context 'gitlab alert'
|
||||
|
||||
it { is_expected.to eq(gitlab_alert) }
|
||||
end
|
||||
|
||||
context 'with unknown gitlab alert' do
|
||||
include_context 'gitlab alert' do
|
||||
let(:gitlab_alert_id) { 'unknown' }
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'when two alerts with the same metric exist' do
|
||||
include_context 'gitlab alert'
|
||||
|
||||
let!(:second_gitlab_alert) do
|
||||
create(:prometheus_alert,
|
||||
project: project,
|
||||
prometheus_metric_id: gitlab_alert.prometheus_metric_id
|
||||
)
|
||||
end
|
||||
|
||||
context 'alert id given in params' do
|
||||
before do
|
||||
payload['labels'] = {
|
||||
'gitlab_alert_id' => gitlab_alert.prometheus_metric_id.to_s,
|
||||
'gitlab_prometheus_alert_id' => second_gitlab_alert.id
|
||||
}
|
||||
end
|
||||
|
||||
it { is_expected.to eq(second_gitlab_alert) }
|
||||
end
|
||||
|
||||
context 'metric id given in params' do
|
||||
# This tests the case when two alerts are found, as metric id
|
||||
# is not unique.
|
||||
|
||||
# Note the metric id was incorrectly named as 'gitlab_alert_id'
|
||||
# in PrometheusAlert#to_param.
|
||||
before do
|
||||
payload['labels'] = { 'gitlab_alert_id' => gitlab_alert.prometheus_metric_id }
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#title' do
|
||||
subject { alert.title }
|
||||
|
||||
it_behaves_like 'parse payload',
|
||||
'annotations/title',
|
||||
'annotations/summary',
|
||||
'labels/alertname'
|
||||
|
||||
context 'with gitlab alert' do
|
||||
include_context 'gitlab alert'
|
||||
|
||||
context 'with annotations/title' do
|
||||
let(:value) { 'annotation title' }
|
||||
|
||||
before do
|
||||
payload['annotations'] = { 'title' => value }
|
||||
end
|
||||
|
||||
it { is_expected.to eq(gitlab_alert.title) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
subject { alert.description }
|
||||
|
||||
it_behaves_like 'parse payload', 'annotations/description'
|
||||
end
|
||||
|
||||
describe '#annotations' do
|
||||
subject { alert.annotations }
|
||||
|
||||
context 'without payload' do
|
||||
it { is_expected.to eq([]) }
|
||||
end
|
||||
|
||||
context 'with payload' do
|
||||
before do
|
||||
payload['annotations'] = { 'foo' => 'value1', 'bar' => 'value2' }
|
||||
end
|
||||
|
||||
it 'parses annotations' do
|
||||
expect(subject.size).to eq(2)
|
||||
expect(subject.map(&:label)).to eq(%w[foo bar])
|
||||
expect(subject.map(&:value)).to eq(%w[value1 value2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#environment' do
|
||||
subject { alert.environment }
|
||||
|
||||
context 'without gitlab_alert' do
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'with gitlab alert' do
|
||||
include_context 'gitlab alert'
|
||||
|
||||
it { is_expected.to eq(gitlab_alert.environment) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#starts_at' do
|
||||
subject { alert.starts_at }
|
||||
|
||||
context 'with empty startsAt' do
|
||||
before do
|
||||
payload['startsAt'] = nil
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'with invalid startsAt' do
|
||||
before do
|
||||
payload['startsAt'] = 'invalid'
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'with payload' do
|
||||
let(:time) { Time.current.change(usec: 0) }
|
||||
|
||||
before do
|
||||
payload['startsAt'] = time.rfc3339
|
||||
end
|
||||
|
||||
it { is_expected.to eq(time) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#full_query' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
subject { alert.full_query }
|
||||
|
||||
where(:generator_url, :expected_query) do
|
||||
nil | nil
|
||||
'http://localhost' | nil
|
||||
'invalid url' | nil
|
||||
'http://localhost:9090/graph?g1.expr=vector%281%29' | nil
|
||||
'http://localhost:9090/graph?g0.expr=vector%281%29' | 'vector(1)'
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
payload['generatorURL'] = generator_url
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected_query) }
|
||||
end
|
||||
|
||||
context 'with gitlab alert' do
|
||||
include_context 'gitlab alert'
|
||||
include_context 'full query'
|
||||
|
||||
it { is_expected.to eq(gitlab_alert.full_query) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#y_label' do
|
||||
subject { alert.y_label }
|
||||
|
||||
it_behaves_like 'parse payload', 'annotations/gitlab_y_label'
|
||||
|
||||
context 'when y_label is not included in the payload' do
|
||||
it_behaves_like 'parse payload', 'annotations/title'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#alert_markdown' do
|
||||
subject { alert.alert_markdown }
|
||||
|
||||
it_behaves_like 'parse payload', 'annotations/gitlab_incident_markdown'
|
||||
end
|
||||
|
||||
describe '#gitlab_fingerprint' do
|
||||
subject { alert.gitlab_fingerprint }
|
||||
|
||||
context 'when the alert is a GitLab managed alert' do
|
||||
include_context 'gitlab alert'
|
||||
|
||||
it 'returns a fingerprint' do
|
||||
plain_fingerprint = [alert.metric_id, alert.starts_at_raw].join('/')
|
||||
|
||||
is_expected.to eq(Digest::SHA1.hexdigest(plain_fingerprint))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the alert is from self managed Prometheus' do
|
||||
include_context 'full query'
|
||||
|
||||
it 'returns a fingerprint' do
|
||||
plain_fingerprint = [alert.starts_at_raw, alert.title, alert.full_query].join('/')
|
||||
|
||||
is_expected.to eq(Digest::SHA1.hexdigest(plain_fingerprint))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
before do
|
||||
payload.update(
|
||||
'annotations' => { 'title' => 'some title' },
|
||||
'startsAt' => Time.current.rfc3339
|
||||
)
|
||||
end
|
||||
|
||||
subject { alert }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
|
||||
context 'without project' do
|
||||
let(:project) { nil }
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'without starts_at' do
|
||||
before do
|
||||
payload['startsAt'] = nil
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -435,6 +435,28 @@ RSpec.describe Gitlab::Danger::Helper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#draft_mr?' do
|
||||
it 'returns false when `gitlab_helper` is unavailable' do
|
||||
expect(helper).to receive(:gitlab_helper).and_return(nil)
|
||||
|
||||
expect(helper).not_to be_draft_mr
|
||||
end
|
||||
|
||||
it 'returns true for a draft MR' do
|
||||
expect(fake_gitlab).to receive(:mr_json)
|
||||
.and_return('title' => 'Draft: My MR title')
|
||||
|
||||
expect(helper).to be_draft_mr
|
||||
end
|
||||
|
||||
it 'returns false for non draft MR' do
|
||||
expect(fake_gitlab).to receive(:mr_json)
|
||||
.and_return('title' => 'My MR title')
|
||||
|
||||
expect(helper).not_to be_draft_mr
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cherry_pick_mr?' do
|
||||
it 'returns false when `gitlab_helper` is unavailable' do
|
||||
expect(helper).to receive(:gitlab_helper).and_return(nil)
|
||||
|
|
|
@ -30,35 +30,104 @@ RSpec.describe Emails::Projects do
|
|||
let_it_be(:user) { create(:user) }
|
||||
|
||||
describe '#prometheus_alert_fired_email' do
|
||||
let(:default_title) { Gitlab::AlertManagement::Payload::Generic::DEFAULT_TITLE }
|
||||
let(:payload) { { 'startsAt' => Time.now.rfc3339 } }
|
||||
let(:alert_attributes) { build(:alert_management_alert, :from_payload, payload: payload, project: project).attributes }
|
||||
|
||||
subject do
|
||||
Notify.prometheus_alert_fired_email(project.id, user.id, alert_params)
|
||||
Notify.prometheus_alert_fired_email(project.id, user.id, alert_attributes)
|
||||
end
|
||||
|
||||
let(:alert_params) do
|
||||
{ 'startsAt' => Time.now.rfc3339 }
|
||||
context 'missing required attributes' do
|
||||
let(:alert_attributes) { build(:alert_management_alert, :prometheus, :from_payload, payload: payload, project: project).attributes }
|
||||
|
||||
it_behaves_like 'no email'
|
||||
end
|
||||
|
||||
context 'with a gitlab alert' do
|
||||
before do
|
||||
alert_params['labels'] = { 'gitlab_alert_id' => alert.prometheus_metric_id.to_s }
|
||||
end
|
||||
|
||||
let(:title) do
|
||||
"#{alert.title} #{alert.computed_operator} #{alert.threshold}"
|
||||
end
|
||||
|
||||
let(:metrics_url) do
|
||||
metrics_project_environment_url(project, environment)
|
||||
end
|
||||
|
||||
let(:environment) { alert.environment }
|
||||
|
||||
let!(:alert) { create(:prometheus_alert, project: project) }
|
||||
context 'with minimum required attributes' do
|
||||
let(:payload) { {} }
|
||||
|
||||
it_behaves_like 'an email sent from GitLab'
|
||||
it_behaves_like 'it should not have Gmail Actions links'
|
||||
it_behaves_like 'a user cannot unsubscribe through footer link'
|
||||
|
||||
it 'has expected subject' do
|
||||
is_expected.to have_subject("#{project.name} | Alert: #{default_title}")
|
||||
end
|
||||
|
||||
it 'has expected content' do
|
||||
is_expected.to have_body_text('An alert has been triggered')
|
||||
is_expected.to have_body_text(project.full_path)
|
||||
is_expected.not_to have_body_text('Description:')
|
||||
is_expected.not_to have_body_text('Environment:')
|
||||
is_expected.not_to have_body_text('Metric:')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with description' do
|
||||
let(:payload) { { 'description' => 'alert description' } }
|
||||
|
||||
it_behaves_like 'an email sent from GitLab'
|
||||
it_behaves_like 'it should not have Gmail Actions links'
|
||||
it_behaves_like 'a user cannot unsubscribe through footer link'
|
||||
|
||||
it 'has expected subject' do
|
||||
is_expected.to have_subject("#{project.name} | Alert: #{default_title}")
|
||||
end
|
||||
|
||||
it 'has expected content' do
|
||||
is_expected.to have_body_text('An alert has been triggered')
|
||||
is_expected.to have_body_text(project.full_path)
|
||||
is_expected.to have_body_text('Description:')
|
||||
is_expected.to have_body_text('alert description')
|
||||
is_expected.not_to have_body_text('Environment:')
|
||||
is_expected.not_to have_body_text('Metric:')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with environment' do
|
||||
let_it_be(:environment) { create(:environment, project: project) }
|
||||
let(:payload) { { 'gitlab_environment_name' => environment.name } }
|
||||
let(:metrics_url) { metrics_project_environment_url(project, environment) }
|
||||
|
||||
it_behaves_like 'an email sent from GitLab'
|
||||
it_behaves_like 'it should not have Gmail Actions links'
|
||||
it_behaves_like 'a user cannot unsubscribe through footer link'
|
||||
|
||||
it 'has expected subject' do
|
||||
is_expected.to have_subject("#{project.name} | Alert: #{environment.name}: #{default_title}")
|
||||
end
|
||||
|
||||
it 'has expected content' do
|
||||
is_expected.to have_body_text('An alert has been triggered')
|
||||
is_expected.to have_body_text(project.full_path)
|
||||
is_expected.to have_body_text('Environment:')
|
||||
is_expected.to have_body_text(environment.name)
|
||||
is_expected.not_to have_body_text('Description:')
|
||||
is_expected.not_to have_body_text('Metric:')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with gitlab alerting rule' do
|
||||
let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) }
|
||||
let_it_be(:environment) { prometheus_alert.environment }
|
||||
|
||||
let(:alert_attributes) { build(:alert_management_alert, :prometheus, :from_payload, payload: payload, project: project).attributes }
|
||||
let(:title) { "#{prometheus_alert.title} #{prometheus_alert.computed_operator} #{prometheus_alert.threshold}" }
|
||||
let(:metrics_url) { metrics_project_environment_url(project, environment) }
|
||||
|
||||
before do
|
||||
payload['labels'] = {
|
||||
'gitlab_alert_id' => prometheus_alert.prometheus_metric_id,
|
||||
'alertname' => prometheus_alert.title
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'an email sent from GitLab'
|
||||
it_behaves_like 'it should not have Gmail Actions links'
|
||||
it_behaves_like 'a user cannot unsubscribe through footer link'
|
||||
it_behaves_like 'shows the incident issues url'
|
||||
|
||||
it 'has expected subject' do
|
||||
is_expected.to have_subject("#{project.name} | Alert: #{environment.name}: #{title} for 5 minutes")
|
||||
end
|
||||
|
@ -69,68 +138,10 @@ RSpec.describe Emails::Projects do
|
|||
is_expected.to have_body_text('Environment:')
|
||||
is_expected.to have_body_text(environment.name)
|
||||
is_expected.to have_body_text('Metric:')
|
||||
is_expected.to have_body_text(alert.full_query)
|
||||
is_expected.to have_body_text(prometheus_alert.full_query)
|
||||
is_expected.to have_body_text(metrics_url)
|
||||
end
|
||||
|
||||
it_behaves_like 'shows the incident issues url'
|
||||
end
|
||||
|
||||
context 'with no payload' do
|
||||
let(:alert_params) { {} }
|
||||
|
||||
it_behaves_like 'no email'
|
||||
end
|
||||
|
||||
context 'with an unknown alert' do
|
||||
before do
|
||||
alert_params['labels'] = { 'gitlab_alert_id' => 'unknown' }
|
||||
end
|
||||
|
||||
it_behaves_like 'no email'
|
||||
end
|
||||
|
||||
context 'with an external alert' do
|
||||
let(:title) { 'alert title' }
|
||||
|
||||
let(:metrics_url) do
|
||||
metrics_project_environments_url(project)
|
||||
end
|
||||
|
||||
before do
|
||||
alert_params['annotations'] = { 'title' => title }
|
||||
alert_params['generatorURL'] = 'http://localhost:9090/graph?g0.expr=vector%281%29&g0.tab=1'
|
||||
end
|
||||
|
||||
it_behaves_like 'an email sent from GitLab'
|
||||
it_behaves_like 'it should not have Gmail Actions links'
|
||||
it_behaves_like 'a user cannot unsubscribe through footer link'
|
||||
|
||||
it 'has expected subject' do
|
||||
is_expected.to have_subject("#{project.name} | Alert: #{title}")
|
||||
end
|
||||
|
||||
it 'has expected content' do
|
||||
is_expected.to have_body_text('An alert has been triggered')
|
||||
is_expected.to have_body_text(project.full_path)
|
||||
is_expected.not_to have_body_text('Description:')
|
||||
is_expected.not_to have_body_text('Environment:')
|
||||
end
|
||||
|
||||
context 'with annotated description' do
|
||||
let(:description) { 'description' }
|
||||
|
||||
before do
|
||||
alert_params['annotations']['description'] = description
|
||||
end
|
||||
|
||||
it 'shows the description' do
|
||||
is_expected.to have_body_text('Description:')
|
||||
is_expected.to have_body_text(description)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'shows the incident issues url'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -824,7 +824,7 @@ RSpec.describe Issuable do
|
|||
|
||||
where(:issuable_type, :supports_time_tracking) do
|
||||
:issue | true
|
||||
:incident | false
|
||||
:incident | true
|
||||
:merge_request | true
|
||||
end
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ RSpec.describe ReactiveCaching, :use_clean_rails_memory_store_caching do
|
|||
|
||||
self.reactive_cache_lifetime = 5.minutes
|
||||
self.reactive_cache_refresh_interval = 15.seconds
|
||||
self.reactive_cache_work_type = :no_dependency
|
||||
|
||||
attr_reader :id
|
||||
|
||||
|
@ -372,4 +373,14 @@ RSpec.describe ReactiveCaching, :use_clean_rails_memory_store_caching do
|
|||
it { expect(subject.reactive_cache_hard_limit).to be_nil }
|
||||
it { expect(subject.reactive_cache_worker_finder).to respond_to(:call) }
|
||||
end
|
||||
|
||||
describe 'classes including this concern' do
|
||||
it 'sets reactive_cache_work_type' do
|
||||
classes = ObjectSpace.each_object(Class).select do |klass|
|
||||
klass < described_class && klass.name
|
||||
end
|
||||
|
||||
expect(classes).to all(have_attributes(reactive_cache_work_type: be_in(described_class::WORK_TYPE.keys)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,346 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::Prometheus::AlertPresenter do
|
||||
include Gitlab::Routing.url_helpers
|
||||
|
||||
let_it_be(:project, reload: true) { create(:project) }
|
||||
|
||||
let(:presenter) { described_class.new(alert) }
|
||||
let(:payload) { {} }
|
||||
let(:alert) { create(:alerting_alert, project: project, payload: payload) }
|
||||
|
||||
shared_context 'gitlab alert' do
|
||||
let(:gitlab_alert) { create(:prometheus_alert, project: project) }
|
||||
let(:metric_id) { gitlab_alert.prometheus_metric_id }
|
||||
|
||||
let(:alert) do
|
||||
create(:alerting_alert, project: project, metric_id: metric_id, payload: payload)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#project_full_path' do
|
||||
subject { presenter.project_full_path }
|
||||
|
||||
it { is_expected.to eq(project.full_path) }
|
||||
end
|
||||
|
||||
describe '#start_time' do
|
||||
subject { presenter.start_time }
|
||||
|
||||
let(:starts_at) { '2020-10-31T14:02:04Z' }
|
||||
|
||||
before do
|
||||
payload['startsAt'] = starts_at
|
||||
end
|
||||
|
||||
context 'with valid utc datetime' do
|
||||
it { is_expected.to eq('31 October 2020, 2:02PM (UTC)') }
|
||||
|
||||
context 'with admin time zone not UTC' do
|
||||
before do
|
||||
allow(Time).to receive(:zone).and_return(ActiveSupport::TimeZone.new('Perth'))
|
||||
end
|
||||
|
||||
it { is_expected.to eq('31 October 2020, 2:02PM (UTC)') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid datetime' do
|
||||
let(:starts_at) { 'invalid' }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#issue_summary_markdown' do
|
||||
let(:markdown_line_break) { ' ' }
|
||||
|
||||
subject { presenter.issue_summary_markdown }
|
||||
|
||||
context 'without default payload' do
|
||||
it do
|
||||
is_expected.to eq(
|
||||
<<~MARKDOWN.chomp
|
||||
**Start time:** #{presenter.start_time}
|
||||
|
||||
MARKDOWN
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with optional attributes' do
|
||||
before do
|
||||
payload['annotations'] = {
|
||||
'title' => 'Alert Title',
|
||||
'foo' => 'value1',
|
||||
'bar' => 'value2',
|
||||
'description' => 'Alert Description',
|
||||
'monitoring_tool' => 'monitoring_tool_name',
|
||||
'service' => 'service_name',
|
||||
'hosts' => ['http://localhost:3000', 'http://localhost:3001']
|
||||
}
|
||||
payload['generatorURL'] = 'http://host?g0.expr=query'
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to eq(
|
||||
<<~MARKDOWN.chomp
|
||||
**Start time:** #{presenter.start_time}#{markdown_line_break}
|
||||
**full_query:** `query`#{markdown_line_break}
|
||||
**Service:** service_name#{markdown_line_break}
|
||||
**Monitoring tool:** monitoring_tool_name#{markdown_line_break}
|
||||
**Hosts:** http://localhost:3000 http://localhost:3001
|
||||
|
||||
MARKDOWN
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when hosts is a string' do
|
||||
before do
|
||||
payload['annotations'] = { 'hosts' => 'http://localhost:3000' }
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to eq(
|
||||
<<~MARKDOWN.chomp
|
||||
**Start time:** #{presenter.start_time}#{markdown_line_break}
|
||||
**Hosts:** http://localhost:3000
|
||||
|
||||
MARKDOWN
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with embedded metrics' do
|
||||
let(:starts_at) { '2018-03-12T09:06:00Z' }
|
||||
|
||||
shared_examples_for 'markdown with metrics embed' do
|
||||
let(:embed_regex) { /\n\[\]\(#{Regexp.quote(presenter.metrics_dashboard_url)}\)\z/ }
|
||||
|
||||
context 'without a starting time available' do
|
||||
around do |example|
|
||||
Timecop.freeze(starts_at) { example.run }
|
||||
end
|
||||
|
||||
before do
|
||||
payload.delete('startsAt')
|
||||
end
|
||||
|
||||
it { is_expected.to match(embed_regex) }
|
||||
end
|
||||
|
||||
context 'with a starting time available' do
|
||||
it { is_expected.to match(embed_regex) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'for gitlab-managed prometheus alerts' do
|
||||
include_context 'gitlab-managed prometheus alert attributes'
|
||||
|
||||
let(:alert) do
|
||||
create(:alerting_alert, project: project, metric_id: prometheus_metric_id, payload: payload)
|
||||
end
|
||||
|
||||
it_behaves_like 'markdown with metrics embed'
|
||||
end
|
||||
|
||||
context 'for alerts from a self-managed prometheus' do
|
||||
include_context 'self-managed prometheus alert attributes'
|
||||
|
||||
it_behaves_like 'markdown with metrics embed'
|
||||
|
||||
context 'without y_label' do
|
||||
let(:y_label) { title }
|
||||
|
||||
before do
|
||||
payload['annotations'].delete('gitlab_y_label')
|
||||
end
|
||||
|
||||
it_behaves_like 'markdown with metrics embed'
|
||||
end
|
||||
|
||||
context 'when not enough information is present for an embed' do
|
||||
shared_examples_for 'does not include an embed' do
|
||||
it { is_expected.not_to match(/\[\]\(.+\)/) }
|
||||
end
|
||||
|
||||
context 'without title' do
|
||||
before do
|
||||
payload['annotations'].delete('title')
|
||||
end
|
||||
|
||||
it_behaves_like 'does not include an embed'
|
||||
end
|
||||
|
||||
context 'without environment' do
|
||||
before do
|
||||
payload['labels'].delete('gitlab_environment_name')
|
||||
end
|
||||
|
||||
it_behaves_like 'does not include an embed'
|
||||
end
|
||||
|
||||
context 'without full_query' do
|
||||
before do
|
||||
payload.delete('generatorURL')
|
||||
end
|
||||
|
||||
it_behaves_like 'does not include an embed'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#show_performance_dashboard_link?' do
|
||||
subject { presenter.show_performance_dashboard_link? }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
|
||||
context 'with gitlab alert' do
|
||||
include_context 'gitlab alert'
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#show_incident_issues_link?' do
|
||||
subject { presenter.show_incident_issues_link? }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
|
||||
context 'create issue setting enabled' do
|
||||
before do
|
||||
create(:project_incident_management_setting, project: project, create_issue: true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#details_url' do
|
||||
subject { presenter.details_url }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
|
||||
context 'alert management alert present' do
|
||||
let_it_be(:am_alert) { create(:alert_management_alert, project: project) }
|
||||
let(:alert) { create(:alerting_alert, project: project, payload: payload, am_alert: am_alert) }
|
||||
|
||||
it { is_expected.to eq("http://localhost/#{project.full_path}/-/alert_management/#{am_alert.iid}/details") }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with gitlab alert' do
|
||||
include_context 'gitlab alert'
|
||||
|
||||
describe '#full_title' do
|
||||
let(:query_title) do
|
||||
"#{gitlab_alert.title} #{gitlab_alert.computed_operator} #{gitlab_alert.threshold} for 5 minutes"
|
||||
end
|
||||
|
||||
let(:expected_subject) do
|
||||
"#{alert.environment.name}: #{query_title}"
|
||||
end
|
||||
|
||||
subject { presenter.full_title }
|
||||
|
||||
it { is_expected.to eq(expected_subject) }
|
||||
end
|
||||
|
||||
describe '#metric_query' do
|
||||
subject { presenter.metric_query }
|
||||
|
||||
it { is_expected.to eq(gitlab_alert.full_query) }
|
||||
end
|
||||
|
||||
describe '#environment_name' do
|
||||
subject { presenter.environment_name }
|
||||
|
||||
it { is_expected.to eq(alert.environment.name) }
|
||||
end
|
||||
|
||||
describe '#performance_dashboard_link' do
|
||||
let(:expected_link) { metrics_project_environment_url(project, alert.environment) }
|
||||
|
||||
subject { presenter.performance_dashboard_link }
|
||||
|
||||
it { is_expected.to eq(expected_link) }
|
||||
end
|
||||
|
||||
describe '#incident_issues_link' do
|
||||
let(:expected_link) { project_issues_url(project, label_name: described_class::INCIDENT_LABEL_NAME) }
|
||||
|
||||
subject { presenter.incident_issues_link }
|
||||
|
||||
it { is_expected.to eq(expected_link) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'without gitlab alert' do
|
||||
describe '#full_title' do
|
||||
subject { presenter.full_title }
|
||||
|
||||
context 'with title' do
|
||||
let(:title) { 'some title' }
|
||||
|
||||
before do
|
||||
expect(alert).to receive(:title).and_return(title)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(title) }
|
||||
end
|
||||
|
||||
context 'without title' do
|
||||
it { is_expected.to eq('') }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#metric_query' do
|
||||
subject { presenter.metric_query }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
describe '#environment_name' do
|
||||
subject { presenter.environment_name }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
describe '#performance_dashboard_link' do
|
||||
let(:expected_link) { metrics_project_environments_url(project) }
|
||||
|
||||
subject { presenter.performance_dashboard_link }
|
||||
|
||||
it { is_expected.to eq(expected_link) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#metrics_dashboard_url' do
|
||||
subject { presenter.metrics_dashboard_url }
|
||||
|
||||
context 'for a non-prometheus alert' do
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'for a self-managed prometheus alert' do
|
||||
include_context 'self-managed prometheus alert attributes'
|
||||
|
||||
let(:prometheus_payload) { payload }
|
||||
|
||||
it { is_expected.to eq(dashboard_url_for_alert) }
|
||||
end
|
||||
|
||||
context 'for a gitlab-managed prometheus alert' do
|
||||
include_context 'gitlab-managed prometheus alert attributes'
|
||||
|
||||
let(:prometheus_payload) { payload }
|
||||
|
||||
it { is_expected.to eq(dashboard_url_for_alert) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3082,32 +3082,25 @@ RSpec.describe NotificationService, :mailer do
|
|||
|
||||
describe '#prometheus_alerts_fired' do
|
||||
let!(:project) { create(:project) }
|
||||
let!(:prometheus_alert) { create(:prometheus_alert, project: project) }
|
||||
let!(:master) { create(:user) }
|
||||
let!(:developer) { create(:user) }
|
||||
let(:alert_attributes) { build(:alert_management_alert, project: project).attributes }
|
||||
|
||||
before do
|
||||
project.add_maintainer(master)
|
||||
end
|
||||
|
||||
it 'sends the email to owners and masters' do
|
||||
expect(Notify).to receive(:prometheus_alert_fired_email).with(project.id, master.id, prometheus_alert).and_call_original
|
||||
expect(Notify).to receive(:prometheus_alert_fired_email).with(project.id, project.owner.id, prometheus_alert).and_call_original
|
||||
expect(Notify).not_to receive(:prometheus_alert_fired_email).with(project.id, developer.id, prometheus_alert)
|
||||
expect(Notify).to receive(:prometheus_alert_fired_email).with(project.id, master.id, alert_attributes).and_call_original
|
||||
expect(Notify).to receive(:prometheus_alert_fired_email).with(project.id, project.owner.id, alert_attributes).and_call_original
|
||||
expect(Notify).not_to receive(:prometheus_alert_fired_email).with(project.id, developer.id, alert_attributes)
|
||||
|
||||
subject.prometheus_alerts_fired(prometheus_alert.project, [prometheus_alert])
|
||||
subject.prometheus_alerts_fired(project, [alert_attributes])
|
||||
end
|
||||
|
||||
it_behaves_like 'project emails are disabled' do
|
||||
before do
|
||||
allow_next_instance_of(::Gitlab::Alerting::Alert) do |instance|
|
||||
allow(instance).to receive(:valid?).and_return(true)
|
||||
end
|
||||
end
|
||||
|
||||
let(:alert_params) { { 'labels' => { 'gitlab_alert_id' => 'unknown' } } }
|
||||
let(:notification_target) { prometheus_alert.project }
|
||||
let(:notification_trigger) { subject.prometheus_alerts_fired(prometheus_alert.project, [alert_params]) }
|
||||
let(:notification_target) { project }
|
||||
let(:notification_trigger) { subject.prometheus_alerts_fired(project, [alert_attributes]) }
|
||||
|
||||
around do |example|
|
||||
perform_enqueued_jobs { example.run }
|
||||
|
|
|
@ -197,11 +197,10 @@ RSpec.describe Projects::Alerting::NotifyService do
|
|||
end
|
||||
|
||||
context 'with overlong payload' do
|
||||
let(:payload_raw) do
|
||||
{
|
||||
title: 'a' * Gitlab::Utils::DeepSize::DEFAULT_MAX_SIZE,
|
||||
start_time: starts_at.rfc3339
|
||||
}
|
||||
let(:deep_size_object) { instance_double(Gitlab::Utils::DeepSize, valid?: false) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Utils::DeepSize).to receive(:new).and_return(deep_size_object)
|
||||
end
|
||||
|
||||
it_behaves_like 'does not process incident issues due to error', http_status: :bad_request
|
||||
|
@ -215,17 +214,6 @@ RSpec.describe Projects::Alerting::NotifyService do
|
|||
|
||||
it_behaves_like 'processes incident issues'
|
||||
|
||||
context 'with an invalid payload' do
|
||||
before do
|
||||
allow(Gitlab::Alerting::NotificationPayloadParser)
|
||||
.to receive(:call)
|
||||
.and_raise(Gitlab::Alerting::NotificationPayloadParser::BadPayloadError)
|
||||
end
|
||||
|
||||
it_behaves_like 'does not process incident issues due to error', http_status: :bad_request
|
||||
it_behaves_like 'does not an create alert management alert'
|
||||
end
|
||||
|
||||
context 'when alert already exists' do
|
||||
let(:fingerprint_sha) { Digest::SHA1.hexdigest(fingerprint) }
|
||||
let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
require 'simplecov'
|
||||
require 'simplecov-cobertura'
|
||||
require 'active_support/core_ext/numeric/time'
|
||||
require_relative '../lib/gitlab/utils'
|
||||
|
||||
module SimpleCovEnv
|
||||
|
@ -75,7 +74,7 @@ module SimpleCovEnv
|
|||
add_group 'Libraries', %w[/lib /ee/lib]
|
||||
add_group 'Tooling', %w[/haml_lint /rubocop /tooling]
|
||||
|
||||
merge_timeout 365.days
|
||||
merge_timeout 365 * 24 * 3600
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ RSpec.describe IncidentManagement::ProcessPrometheusAlertWorker do
|
|||
describe '#perform' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) }
|
||||
let(:payload_key) { Gitlab::Alerting::Alert.new(project: project, payload: alert_params).gitlab_fingerprint }
|
||||
let(:payload_key) { Gitlab::AlertManagement::Payload::Prometheus.new(project: project, payload: alert_params).gitlab_fingerprint }
|
||||
let!(:prometheus_alert_event) { create(:prometheus_alert_event, prometheus_alert: prometheus_alert, payload_key: payload_key) }
|
||||
let!(:settings) { create(:project_incident_management_setting, project: project, create_issue: true) }
|
||||
|
||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -12302,10 +12302,10 @@ vue-jest@4.0.0-beta.2:
|
|||
source-map "^0.5.6"
|
||||
ts-jest "^23.10.5"
|
||||
|
||||
vue-loader@^15.9.0:
|
||||
version "15.9.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.0.tgz#5d4b0378a4606188fc83e587ed23c94bc3a10998"
|
||||
integrity sha512-FeDHvTSpwyLeF7LIV1PYkvqUQgTJ8UmOxhSlCyRSxaXCKk+M6NF4tDQsLsPPNeDPyR7TfRQ8MLg6v+8PsDV9xQ==
|
||||
vue-loader@^15.9.3:
|
||||
version "15.9.3"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.3.tgz#0de35d9e555d3ed53969516cac5ce25531299dda"
|
||||
integrity sha512-Y67VnGGgVLH5Voostx8JBZgPQTlDQeOVBLOEsjc2cXbCYBKexSKEpOA56x0YZofoDOTszrLnIShyOX1p9uCEHA==
|
||||
dependencies:
|
||||
"@vue/component-compiler-utils" "^3.1.0"
|
||||
hash-sum "^1.0.2"
|
||||
|
@ -12331,10 +12331,10 @@ vue-style-loader@^4.1.0:
|
|||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.0.2"
|
||||
|
||||
vue-template-compiler@^2.5.20, vue-template-compiler@^2.6.10:
|
||||
version "2.6.10"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
|
||||
integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==
|
||||
vue-template-compiler@^2.5.20, vue-template-compiler@^2.6.12:
|
||||
version "2.6.12"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz#947ed7196744c8a5285ebe1233fe960437fcc57e"
|
||||
integrity sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==
|
||||
dependencies:
|
||||
de-indent "^1.0.2"
|
||||
he "^1.1.0"
|
||||
|
@ -12349,10 +12349,10 @@ vue-virtual-scroll-list@^1.4.4:
|
|||
resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.4.4.tgz#5fca7a13f785899bbfb70471ec4fe222437d8495"
|
||||
integrity sha512-wU7FDpd9Xy4f62pf8SBg/ak21jMI/pdx4s4JPah+z/zuhmeAafQgp8BjtZvvt+b0BZOsOS1FJuCfUH7azTkivQ==
|
||||
|
||||
vue@^2.6.10:
|
||||
version "2.6.10"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
|
||||
integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
|
||||
vue@^2.6.12:
|
||||
version "2.6.12"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123"
|
||||
integrity sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==
|
||||
|
||||
vuedraggable@^2.23.0:
|
||||
version "2.23.0"
|
||||
|
|
Loading…
Reference in a new issue