diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml index 238059bf972..f0a9c0bfa98 100644 --- a/.gitlab/ci/global.gitlab-ci.yml +++ b/.gitlab/ci/global.gitlab-ci.yml @@ -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" diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 165476678bb..38bcea0df1f 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -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 diff --git a/Gemfile b/Gemfile index 90abfd30900..56e30a33be8 100644 --- a/Gemfile +++ b/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 diff --git a/app/assets/javascripts/packages/shared/components/package_list_row.vue b/app/assets/javascripts/packages/shared/components/package_list_row.vue index f93bc51d185..d55ca80a7fc 100644 --- a/app/assets/javascripts/packages/shared/components/package_list_row.vue +++ b/app/assets/javascripts/packages/shared/components/package_list_row.vue @@ -1,6 +1,7 @@ + + diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index fdf40a77ca4..17ef8b41e79 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -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 diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb index e9b89af45c6..f5e56cb50c9 100644 --- a/app/models/alert_management/alert.rb +++ b/app/models/alert_management/alert.rb @@ -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! diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 7af78960e35..1de7c90e50d 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -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' diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index b285e30f619..7624a1a4e80 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -182,7 +182,7 @@ module Issuable end def supports_time_tracking? - is_a?(TimeTrackable) && !incident? + is_a?(TimeTrackable) end def supports_severity? diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb index 5f30fc0c36c..26f1544103c 100644 --- a/app/models/concerns/reactive_caching.rb +++ b/app/models/concerns/reactive_caching.rb @@ -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 diff --git a/app/models/concerns/reactive_service.rb b/app/models/concerns/reactive_service.rb index af69da24994..c444f238944 100644 --- a/app/models/concerns/reactive_service.rb +++ b/app/models/concerns/reactive_service.rb @@ -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 diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 2e9e57bc4c4..b334da1ca91 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -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 diff --git a/app/presenters/alert_management/alert_presenter.rb b/app/presenters/alert_management/alert_presenter.rb index 5debe6d5dbd..aafc18084fd 100644 --- a/app/presenters/alert_management/alert_presenter.rb +++ b/app/presenters/alert_management/alert_presenter.rb @@ -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 diff --git a/app/presenters/projects/prometheus/alert_presenter.rb b/app/presenters/projects/prometheus/alert_presenter.rb deleted file mode 100644 index 783b2b2b1e0..00000000000 --- a/app/presenters/projects/prometheus/alert_presenter.rb +++ /dev/null @@ -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 diff --git a/app/services/alert_management/process_prometheus_alert_service.rb b/app/services/alert_management/process_prometheus_alert_service.rb index 95ae84a85a4..5c7698f724a 100644 --- a/app/services/alert_management/process_prometheus_alert_service.rb +++ b/app/services/alert_management/process_prometheus_alert_service.rb @@ -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 diff --git a/app/services/pod_logs/base_service.rb b/app/services/pod_logs/base_service.rb index 8936f9b67a5..e4b6ad31e33 100644 --- a/app/services/pod_logs/base_service.rb +++ b/app/services/pod_logs/base_service.rb @@ -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 diff --git a/app/services/pod_logs/elasticsearch_service.rb b/app/services/pod_logs/elasticsearch_service.rb index f79562c8ab3..58d1bfbf835 100644 --- a/app/services/pod_logs/elasticsearch_service.rb +++ b/app/services/pod_logs/elasticsearch_service.rb @@ -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 diff --git a/app/services/pod_logs/kubernetes_service.rb b/app/services/pod_logs/kubernetes_service.rb index b573ceae1aa..03b84f98973 100644 --- a/app/services/pod_logs/kubernetes_service.rb +++ b/app/services/pod_logs/kubernetes_service.rb @@ -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 diff --git a/app/services/projects/alerting/notify_service.rb b/app/services/projects/alerting/notify_service.rb index bfce5f1ad63..8a6c735b1a2 100644 --- a/app/services/projects/alerting/notify_service.rb +++ b/app/services/projects/alerting/notify_service.rb @@ -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) diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb index d32ead76d00..c002aca32db 100644 --- a/app/services/projects/prometheus/alerts/notify_service.rb +++ b/app/services/projects/prometheus/alerts/notify_service.rb @@ -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 diff --git a/app/views/notify/prometheus_alert_fired_email.html.haml b/app/views/notify/prometheus_alert_fired_email.html.haml index 17f9481d353..75ba66b44f9 100644 --- a/app/views/notify/prometheus_alert_fired_email.html.haml +++ b/app/views/notify/prometheus_alert_fired_email.html.haml @@ -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) - diff --git a/app/views/notify/prometheus_alert_fired_email.text.erb b/app/views/notify/prometheus_alert_fired_email.text.erb index c3f005cfb7e..8853f2a317b 100644 --- a/app/views/notify/prometheus_alert_fired_email.text.erb +++ b/app/views/notify/prometheus_alert_fired_email.text.erb @@ -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 %> diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml index f954b09abee..e9dfda4e927 100644 --- a/app/views/projects/diffs/_file_header.html.haml +++ b/app/views/projects/diffs/_file_header.html.haml @@ -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) diff --git a/changelogs/unreleased/225644-package-registry-in-the-group-registry-view-make-the-project-ui-mo.yml b/changelogs/unreleased/225644-package-registry-in-the-group-registry-view-make-the-project-ui-mo.yml new file mode 100644 index 00000000000..cce1d360787 --- /dev/null +++ b/changelogs/unreleased/225644-package-registry-in-the-group-registry-view-make-the-project-ui-mo.yml @@ -0,0 +1,5 @@ +--- +title: Breadcrumb like UI for project path in packages list +merge_request: 42684 +author: +type: changed diff --git a/changelogs/unreleased/254250-add-time-tracking-to-incidents.yml b/changelogs/unreleased/254250-add-time-tracking-to-incidents.yml new file mode 100644 index 00000000000..0f183f66e82 --- /dev/null +++ b/changelogs/unreleased/254250-add-time-tracking-to-incidents.yml @@ -0,0 +1,5 @@ +--- +title: Allow time tracking in incidents +merge_request: 42965 +author: +type: changed diff --git a/config/feature_flags/development/boards_with_swimlanes.yml b/config/feature_flags/development/boards_with_swimlanes.yml index e2db136b45c..19e5ab24206 100644 --- a/config/feature_flags/development/boards_with_swimlanes.yml +++ b/config/feature_flags/development/boards_with_swimlanes.yml @@ -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 diff --git a/config/feature_flags/development/bulk_update_health_status.yml b/config/feature_flags/development/bulk_update_health_status.yml index 5553f2e077f..fa06adfd60a 100644 --- a/config/feature_flags/development/bulk_update_health_status.yml +++ b/config/feature_flags/development/bulk_update_health_status.yml @@ -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 diff --git a/config/feature_flags/development/burnup_charts.yml b/config/feature_flags/development/burnup_charts.yml index 9484af49530..5ead8362d90 100644 --- a/config/feature_flags/development/burnup_charts.yml +++ b/config/feature_flags/development/burnup_charts.yml @@ -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 diff --git a/config/feature_flags/development/graphql_board_lists.yml b/config/feature_flags/development/graphql_board_lists.yml index 4e6bf000a8f..7e2696a06c8 100644 --- a/config/feature_flags/development/graphql_board_lists.yml +++ b/config/feature_flags/development/graphql_board_lists.yml @@ -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 diff --git a/config/feature_flags/development/multi_select_board.yml b/config/feature_flags/development/multi_select_board.yml index 35718606d62..3849fb0e12b 100644 --- a/config/feature_flags/development/multi_select_board.yml +++ b/config/feature_flags/development/multi_select_board.yml @@ -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 diff --git a/config/feature_flags/development/tribute_autocomplete.yml b/config/feature_flags/development/tribute_autocomplete.yml index 31ee1b932d3..94cfc00c467 100644 --- a/config/feature_flags/development/tribute_autocomplete.yml +++ b/config/feature_flags/development/tribute_autocomplete.yml @@ -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 diff --git a/config/feature_flags/development/vue_issuable_sidebar.yml b/config/feature_flags/development/vue_issuable_sidebar.yml index d57852c9491..01c8bc3460b 100644 --- a/config/feature_flags/development/vue_issuable_sidebar.yml +++ b/config/feature_flags/development/vue_issuable_sidebar.yml @@ -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 diff --git a/config/feature_flags/development/vue_issuables_list.yml b/config/feature_flags/development/vue_issuables_list.yml index 79ade237824..5fe5c7e3e9f 100644 --- a/config/feature_flags/development/vue_issuables_list.yml +++ b/config/feature_flags/development/vue_issuables_list.yml @@ -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 diff --git a/config/feature_flags/development/vue_sidebar_labels.yml b/config/feature_flags/development/vue_sidebar_labels.yml index d786db39487..13b1ae20dd7 100644 --- a/config/feature_flags/development/vue_sidebar_labels.yml +++ b/config/feature_flags/development/vue_sidebar_labels.yml @@ -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 diff --git a/danger/telemetry/Dangerfile b/danger/telemetry/Dangerfile index 6cfc1a005c3..e335f6e0f73 100644 --- a/danger/telemetry/Dangerfile +++ b/danger/telemetry/Dangerfile @@ -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 %s group for a review. %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'] diff --git a/doc/development/reactive_caching.md b/doc/development/reactive_caching.md index f3386305e93..cf125c46565 100644 --- a/doc/development/reactive_caching.md +++ b/doc/development/reactive_caching.md @@ -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` diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md index f40955ad8ff..0c511e3703a 100644 --- a/doc/integration/elasticsearch.md +++ b/doc/integration/elasticsearch.md @@ -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 :9200/gitlab-production/_search?q= + ``` + + 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). diff --git a/lib/gitlab/alert_management/alert_params.rb b/lib/gitlab/alert_management/alert_params.rb deleted file mode 100644 index 3bb839c1114..00000000000 --- a/lib/gitlab/alert_management/alert_params.rb +++ /dev/null @@ -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 diff --git a/lib/gitlab/alert_management/payload/generic.rb b/lib/gitlab/alert_management/payload/generic.rb index 7efdfac75dc..e8e85155bef 100644 --- a/lib/gitlab/alert_management/payload/generic.rb +++ b/lib/gitlab/alert_management/payload/generic.rb @@ -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') diff --git a/lib/gitlab/alerting/alert.rb b/lib/gitlab/alerting/alert.rb deleted file mode 100644 index 94b81b7d290..00000000000 --- a/lib/gitlab/alerting/alert.rb +++ /dev/null @@ -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 diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb index 3626ec5bf5b..d01455c9ec4 100644 --- a/lib/gitlab/danger/helper.rb +++ b/lib/gitlab/danger/helper.rb @@ -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 diff --git a/package.json b/package.json index b9fa6212dc5..1f5d1b9e9cb 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh index 1243609dc24..e95f20bc26c 100644 --- a/scripts/prepare_build.sh +++ b/scripts/prepare_build.sh @@ -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 diff --git a/spec/controllers/projects/import/jira_controller_spec.rb b/spec/controllers/projects/import/jira_controller_spec.rb index b82735a56b3..37a7fce0c23 100644 --- a/spec/controllers/projects/import/jira_controller_spec.rb +++ b/spec/controllers/projects/import/jira_controller_spec.rb @@ -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 diff --git a/spec/factories/alert_management/alerts.rb b/spec/factories/alert_management/alerts.rb index d931947fff1..d0546657ccf 100644 --- a/spec/factories/alert_management/alerts.rb +++ b/spec/factories/alert_management/alerts.rb @@ -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 diff --git a/spec/factories/alerting/alert.rb b/spec/factories/alerting/alert.rb deleted file mode 100644 index 285bb14efa2..00000000000 --- a/spec/factories/alerting/alert.rb +++ /dev/null @@ -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 diff --git a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap index cf5451490eb..5faae5690db 100644 --- a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap +++ b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap @@ -51,27 +51,6 @@ exports[`packages_list_row renders 1`] = ` -
- - - - - -
-
+ + diff --git a/spec/frontend/packages/shared/components/package_list_row_spec.js b/spec/frontend/packages/shared/components/package_list_row_spec.js index f4eabf7bb67..0d0ea4e2122 100644 --- a/spec/frontend/packages/shared/components/package_list_row_spec.js +++ b/spec/frontend/packages/shared/components/package_list_row_spec.js @@ -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' }); }); }); diff --git a/spec/frontend/packages/shared/components/package_path_spec.js b/spec/frontend/packages/shared/components/package_path_spec.js new file mode 100644 index 00000000000..40d455ac77c --- /dev/null +++ b/spec/frontend/packages/shared/components/package_path_spec.js @@ -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]); + }); + } + }); +}); diff --git a/spec/lib/gitlab/alert_management/alert_params_spec.rb b/spec/lib/gitlab/alert_management/alert_params_spec.rb deleted file mode 100644 index c3171be5e29..00000000000 --- a/spec/lib/gitlab/alert_management/alert_params_spec.rb +++ /dev/null @@ -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 diff --git a/spec/lib/gitlab/alert_management/payload/generic_spec.rb b/spec/lib/gitlab/alert_management/payload/generic_spec.rb index 538a822503e..3683dcd752f 100644 --- a/spec/lib/gitlab/alert_management/payload/generic_spec.rb +++ b/spec/lib/gitlab/alert_management/payload/generic_spec.rb @@ -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 diff --git a/spec/lib/gitlab/alerting/alert_spec.rb b/spec/lib/gitlab/alerting/alert_spec.rb deleted file mode 100644 index b53b71e3f3e..00000000000 --- a/spec/lib/gitlab/alerting/alert_spec.rb +++ /dev/null @@ -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 diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb index c7d55c396ef..708e9a13aed 100644 --- a/spec/lib/gitlab/danger/helper_spec.rb +++ b/spec/lib/gitlab/danger/helper_spec.rb @@ -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) diff --git a/spec/mailers/emails/projects_spec.rb b/spec/mailers/emails/projects_spec.rb index 599f62a8113..aa5947bf68e 100644 --- a/spec/mailers/emails/projects_spec.rb +++ b/spec/mailers/emails/projects_spec.rb @@ -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 diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 1f335817d57..431865caf4c 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -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 diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb index b12ad82920f..7e031bdd263 100644 --- a/spec/models/concerns/reactive_caching_spec.rb +++ b/spec/models/concerns/reactive_caching_spec.rb @@ -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 diff --git a/spec/presenters/projects/prometheus/alert_presenter_spec.rb b/spec/presenters/projects/prometheus/alert_presenter_spec.rb deleted file mode 100644 index 98dba28829e..00000000000 --- a/spec/presenters/projects/prometheus/alert_presenter_spec.rb +++ /dev/null @@ -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 diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index af988381017..473a06c4c8c 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -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 } diff --git a/spec/services/projects/alerting/notify_service_spec.rb b/spec/services/projects/alerting/notify_service_spec.rb index 68764990886..24741db40bc 100644 --- a/spec/services/projects/alerting/notify_service_spec.rb +++ b/spec/services/projects/alerting/notify_service_spec.rb @@ -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) } diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb index 17b76205d9e..617a45ae449 100644 --- a/spec/simplecov_env.rb +++ b/spec/simplecov_env.rb @@ -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 diff --git a/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb index c294892a66f..2ca4193aa72 100644 --- a/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb +++ b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb @@ -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) } diff --git a/yarn.lock b/yarn.lock index 0752825b270..a01fb3870af 100644 --- a/yarn.lock +++ b/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"