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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-08-25 21:10:49 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-08-25 21:10:49 +0300
commit2b349d9a94f00320a82e24be96ee8aeda9135702 (patch)
treebf5531e0c78cf0f55c74e2e8651b5a2a7418f9a2
parent11a29f1f026ecd0c466625782af00a14889a2f91 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.haml-lint_todo.yml1
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/stylesheets/mailer.scss16
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/admin/integrations_controller.rb4
-rw-r--r--app/controllers/groups/settings/integrations_controller.rb6
-rw-r--r--app/controllers/invites_controller.rb15
-rw-r--r--app/graphql/resolvers/concerns/issue_resolver_fields.rb4
-rw-r--r--app/graphql/resolvers/issue_status_counts_resolver.rb2
-rw-r--r--app/graphql/resolvers/issues_resolver.rb10
-rw-r--r--app/graphql/types/issue_type.rb5
-rw-r--r--app/graphql/types/project_type.rb2
-rw-r--r--app/mailers/emails/members.rb40
-rw-r--r--app/models/member.rb4
-rw-r--r--app/models/service.rb15
-rw-r--r--app/views/invites/show.html.haml2
-rw-r--r--app/views/layouts/experiment_mailer.html.haml48
-rw-r--r--app/views/notify/member_invited_email.html.haml2
-rw-r--r--app/views/notify/member_invited_email.text.erb2
-rw-r--r--app/views/notify/member_invited_email_experiment.html.haml12
-rw-r--r--app/views/notify/member_invited_email_experiment.text.erb10
-rw-r--r--changelogs/unreleased/240874-expose-alert-via-issues-type.yml5
-rw-r--r--changelogs/unreleased/backup_wrap_concurrency.yml5
-rw-r--r--changelogs/unreleased/bump-puma-to-4-3-5-gitlab-3.yml5
-rw-r--r--doc/administration/pages/index.md2
-rw-r--r--doc/administration/reference_architectures/10k_users.md5
-rw-r--r--doc/administration/reference_architectures/25k_users.md5
-rw-r--r--doc/administration/reference_architectures/2k_users.md5
-rw-r--r--doc/administration/reference_architectures/3k_users.md5
-rw-r--r--doc/administration/reference_architectures/50k_users.md5
-rw-r--r--doc/administration/reference_architectures/5k_users.md5
-rw-r--r--doc/administration/troubleshooting/sidekiq.md2
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql10
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json28
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/development/code_review.md7
-rw-r--r--doc/install/docker.md14
-rw-r--r--doc/integration/elasticsearch.md288
-rw-r--r--lib/backup/repository.rb38
-rw-r--r--lib/gitlab/experimentation.rb3
-rw-r--r--locale/gitlab.pot21
-rw-r--r--qa/qa/runtime/browser.rb2
-rw-r--r--qa/qa/runtime/env.rb4
-rw-r--r--qa/qa/runtime/namespace.rb11
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb2
-rw-r--r--qa/spec/runtime/namespace_spec.rb51
-rw-r--r--spec/controllers/groups/settings/integrations_controller_spec.rb3
-rw-r--r--spec/controllers/invites_controller_spec.rb103
-rw-r--r--spec/factories/issues.rb6
-rw-r--r--spec/graphql/types/issue_type_spec.rb2
-rw-r--r--spec/mailers/notify_spec.rb216
-rw-r--r--spec/models/member_spec.rb18
-rw-r--r--spec/models/service_spec.rb55
-rw-r--r--spec/policies/metrics/dashboard/annotation_policy_spec.rb26
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb36
56 files changed, 932 insertions, 268 deletions
diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml
index bd650e7d6a2..13edc1f024e 100644
--- a/.haml-lint_todo.yml
+++ b/.haml-lint_todo.yml
@@ -117,6 +117,7 @@ linters:
- "app/views/import/bitbucket_server/status.html.haml"
- "app/views/invites/show.html.haml"
- "app/views/layouts/_mailer.html.haml"
+ - "app/views/layouts/experiment_mailer.html.haml"
- "app/views/layouts/header/_default.html.haml"
- "app/views/layouts/header/_new_dropdown.haml"
- "app/views/layouts/notify.html.haml"
diff --git a/Gemfile.lock b/Gemfile.lock
index 40dc62cbc97..13165a1e8b2 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -436,7 +436,7 @@ GEM
gitlab-mail_room (0.0.6)
gitlab-markup (1.7.1)
gitlab-net-dns (0.9.1)
- gitlab-puma (4.3.3.gitlab.2)
+ gitlab-puma (4.3.5.gitlab.3)
nio4r (~> 2.0)
gitlab-puma_worker_killer (0.1.1.gitlab.1)
get_process_mem (~> 0.2)
diff --git a/app/assets/stylesheets/mailer.scss b/app/assets/stylesheets/mailer.scss
index f188b29a113..a5fc92237df 100644
--- a/app/assets/stylesheets/mailer.scss
+++ b/app/assets/stylesheets/mailer.scss
@@ -48,6 +48,22 @@ a {
font-weight: 500;
}
+.invite-header {
+ margin-top: 0;
+}
+
+.invite-actions {
+ margin-top: 24px;
+}
+
+.invite-btn-join {
+ border-radius: $border-radius-default;
+ padding: $gl-btn-vert-padding $gl-btn-horz-padding;
+ cursor: pointer;
+ background-color: $purple;
+ color: $white;
+}
+
tr td {
font-family: $mailer-font;
}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 3a5b8b2862e..7e92940ff30 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -32,7 +32,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
def integrations
- @integrations = Service.find_or_initialize_instances.sort_by(&:title)
+ @integrations = Service.find_or_initialize_all(Service.instances).sort_by(&:title)
end
def update
diff --git a/app/controllers/admin/integrations_controller.rb b/app/controllers/admin/integrations_controller.rb
index b2d5a2d130c..1e2a99f7078 100644
--- a/app/controllers/admin/integrations_controller.rb
+++ b/app/controllers/admin/integrations_controller.rb
@@ -6,9 +6,7 @@ class Admin::IntegrationsController < Admin::ApplicationController
private
def find_or_initialize_integration(name)
- if name.in?(Service.available_services_names)
- "#{name}_service".camelize.constantize.find_or_initialize_by(instance: true) # rubocop:disable CodeReuse/ActiveRecord
- end
+ Service.find_or_initialize_integration(name, instance: true)
end
def integrations_enabled?
diff --git a/app/controllers/groups/settings/integrations_controller.rb b/app/controllers/groups/settings/integrations_controller.rb
index adfbe9bfa17..844b19a4343 100644
--- a/app/controllers/groups/settings/integrations_controller.rb
+++ b/app/controllers/groups/settings/integrations_controller.rb
@@ -8,15 +8,13 @@ module Groups
before_action :authorize_admin_group!
def index
- @integrations = []
+ @integrations = Service.find_or_initialize_all(Service.by_group(group)).sort_by(&:title)
end
private
- # TODO: Make this compatible with group-level integration
- # https://gitlab.com/groups/gitlab-org/-/epics/2543
def find_or_initialize_integration(name)
- Project.first.find_or_initialize_service(name)
+ Service.find_or_initialize_integration(name, group_id: group.id)
end
def integrations_enabled?
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 29cafbbbdb6..1126d72bfef 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -12,11 +12,13 @@ class InvitesController < ApplicationController
respond_to :html
def show
+ track_experiment('opened')
accept if skip_invitation_prompt?
end
def accept
if member.accept_invite!(current_user)
+ track_experiment('accepted')
redirect_to invite_details[:path], notice: _("You have been granted %{member_human_access} access to %{title} %{name}.") %
{ member_human_access: member.human_access, title: invite_details[:title], name: invite_details[:name] }
else
@@ -96,4 +98,17 @@ class InvitesController < ApplicationController
}
end
end
+
+ def track_experiment(action)
+ return unless params[:new_user_invite]
+
+ property = params[:new_user_invite] == 'experiment' ? 'experiment_group' : 'control_group'
+
+ Gitlab::Tracking.event(
+ Gitlab::Experimentation::EXPERIMENTS[:invite_email][:tracking_category],
+ action,
+ property: property,
+ value: Digest::MD5.hexdigest(member.to_global_id.to_s)
+ )
+ end
end
diff --git a/app/graphql/resolvers/concerns/issue_resolver_fields.rb b/app/graphql/resolvers/concerns/issue_resolver_fields.rb
index bf2f510dd89..fe8075fdbd0 100644
--- a/app/graphql/resolvers/concerns/issue_resolver_fields.rb
+++ b/app/graphql/resolvers/concerns/issue_resolver_fields.rb
@@ -4,6 +4,8 @@ module IssueResolverFields
extend ActiveSupport::Concern
prepended do
+ include LooksAhead
+
argument :iid, GraphQL::STRING_TYPE,
required: false,
description: 'IID of the issue. For example, "1"'
@@ -49,7 +51,7 @@ module IssueResolverFields
required: false
end
- def resolve(**args)
+ def resolve_with_lookahead(**args)
# The project could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` of the project to query for issues, so
# make sure it's loaded and not `nil` before continuing.
diff --git a/app/graphql/resolvers/issue_status_counts_resolver.rb b/app/graphql/resolvers/issue_status_counts_resolver.rb
index 466ca538467..71ce3487808 100644
--- a/app/graphql/resolvers/issue_status_counts_resolver.rb
+++ b/app/graphql/resolvers/issue_status_counts_resolver.rb
@@ -7,7 +7,7 @@ module Resolvers
type Types::IssueStatusCountsType, null: true
def continue_issue_resolve(parent, finder, **args)
- Gitlab::IssuablesCountForState.new(finder, parent)
+ apply_lookahead(Gitlab::IssuablesCountForState.new(finder, parent))
end
end
end
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index e2874f6643c..923d22e1be5 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -19,7 +19,7 @@ module Resolvers
milestone_due_asc milestone_due_desc].freeze
def continue_issue_resolve(parent, finder, **args)
- issues = Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all
+ issues = apply_lookahead(Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all)
if non_stable_cursor_sort?(args[:sort])
# Certain complex sorts are not supported by the stable cursor pagination yet.
@@ -30,6 +30,14 @@ module Resolvers
end
end
+ private
+
+ def preloads
+ {
+ alert_management_alert: [:alert_management_alert]
+ }
+ end
+
def non_stable_cursor_sort?(sort)
NON_STABLE_CURSOR_SORTS.include?(sort)
end
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index 0a73ce95424..6df21056367 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -101,6 +101,11 @@ module Types
field :type, Types::IssueTypeEnum, null: true,
method: :issue_type,
description: 'Type of the issue'
+
+ field :alert_management_alert,
+ Types::AlertManagement::AlertType,
+ null: true,
+ description: 'Alert associated to this issue'
end
end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 69326e3e73b..8774cf904f0 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -146,12 +146,14 @@ module Types
Types::IssueType.connection_type,
null: true,
description: 'Issues of the project',
+ extras: [:lookahead],
resolver: Resolvers::IssuesResolver
field :issue_status_counts,
Types::IssueStatusCountsType,
null: true,
description: 'Counts of issues by status for the project',
+ extras: [:lookahead],
resolver: Resolvers::IssueStatusCountsResolver
field :milestones, Types::MilestoneType.connection_type, null: true,
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 07ce9bba784..3a13c5949bd 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -51,9 +51,32 @@ module Emails
return unless member_exists?
- member_email_with_layout(
- to: member.invite_email,
- subject: subject("Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}"))
+ subject_line = subject("Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}")
+
+ if member.invite_to_unknown_user? && Feature.enabled?(:invite_email_experiment)
+ subject_line = subject("#{member.created_by.name} invited you to join GitLab") if member.created_by
+ @invite_url_params = { new_user_invite: 'experiment' }
+
+ member_email_with_layout(
+ to: member.invite_email,
+ subject: subject_line,
+ template: 'member_invited_email_experiment',
+ layout: 'experiment_mailer'
+ )
+
+ Gitlab::Tracking.event(Gitlab::Experimentation::EXPERIMENTS[:invite_email][:tracking_category], 'sent', property: 'experiment_group')
+ else
+ @invite_url_params = member.invite_to_unknown_user? ? { new_user_invite: 'control' } : {}
+
+ member_email_with_layout(
+ to: member.invite_email,
+ subject: subject_line
+ )
+
+ if member.invite_to_unknown_user?
+ Gitlab::Tracking.event(Gitlab::Experimentation::EXPERIMENTS[:invite_email][:tracking_category], 'sent', property: 'control_group')
+ end
+ end
end
def member_invite_accepted_email(member_source_type, member_id)
@@ -107,10 +130,15 @@ module Emails
@member_source_type.classify.constantize
end
- def member_email_with_layout(to:, subject:)
+ def member_email_with_layout(to:, subject:, template: nil, layout: 'mailer')
mail(to: to, subject: subject) do |format|
- format.html { render layout: 'mailer' }
- format.text { render layout: 'mailer' }
+ if template
+ format.html { render template, layout: layout }
+ format.text { render template, layout: layout }
+ else
+ format.html { render layout: layout }
+ format.text { render layout: layout }
+ end
end
end
end
diff --git a/app/models/member.rb b/app/models/member.rb
index 2c62ea55785..a27076b49d7 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -395,6 +395,10 @@ class Member < ApplicationRecord
end
end
+ def invite_to_unknown_user?
+ invite? && user_id.nil?
+ end
+
private
def send_invite
diff --git a/app/models/service.rb b/app/models/service.rb
index 5ba91eabab3..b877f8b4143 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -63,6 +63,7 @@ class Service < ApplicationRecord
scope :active, -> { where(active: true) }
scope :by_type, -> (type) { where(type: type) }
scope :by_active_flag, -> (flag) { where(active: flag) }
+ scope :by_group, -> (group) { where(group_id: group, type: available_services_types) }
scope :templates, -> { where(template: true, type: available_services_types) }
scope :instances, -> { where(instance: true, type: available_services_types) }
@@ -305,12 +306,18 @@ class Service < ApplicationRecord
end
end
- def self.find_or_initialize_instances
- instances + build_nonexistent_instances
+ def self.find_or_initialize_integration(name, instance: false, group_id: nil)
+ if name.in?(available_services_names)
+ "#{name}_service".camelize.constantize.find_or_initialize_by(instance: instance, group_id: group_id)
+ end
+ end
+
+ def self.find_or_initialize_all(scope)
+ scope + build_nonexistent_services_for(scope)
end
- private_class_method def self.build_nonexistent_instances
- list_nonexistent_services_for(instances).map do |service_type|
+ private_class_method def self.build_nonexistent_services_for(scope)
+ list_nonexistent_services_for(scope).map do |service_type|
service_type.constantize.new
end
end
diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml
index 283683511d7..6b3996bee76 100644
--- a/app/views/invites/show.html.haml
+++ b/app/views/invites/show.html.haml
@@ -25,5 +25,5 @@
- if !member?
.actions
- = link_to _("Accept invitation"), accept_invite_url(@token), method: :post, class: "btn btn-success"
+ = link_to _("Accept invitation"), accept_invite_url(@token, new_user_invite: params[:new_user_invite]), method: :post, class: "btn btn-success"
= link_to _("Decline"), decline_invite_url(@token), method: :post, class: "btn btn-danger gl-ml-3"
diff --git a/app/views/layouts/experiment_mailer.html.haml b/app/views/layouts/experiment_mailer.html.haml
new file mode 100644
index 00000000000..5a342c400d6
--- /dev/null
+++ b/app/views/layouts/experiment_mailer.html.haml
@@ -0,0 +1,48 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+%html{ lang: "en" }
+ %head
+ %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/
+ %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/
+ %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/
+ %title= message.subject
+
+ -# Avoid premailer processing of client-specific styles (@media tag not supported)
+ -# We need to inline the contents here because mail clients (e.g. iOS Mail, Outlook)
+ -# do not support linked stylesheets.
+ %style{ type: 'text/css', 'data-premailer': 'ignore' }
+ = asset_to_string('mailer_client_specific.css').html_safe
+
+ = stylesheet_link_tag 'mailer.css'
+ %body
+ %table#body{ border: "0", cellpadding: "0", cellspacing: "0" }
+ %tbody
+ %tr.line
+ %td
+ %tr.header
+ %td
+ = html_header_message
+ = header_logo
+ %tr
+ %td
+ %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0" }
+ %tbody
+ %tr
+ %td.wrapper-cell{ style: "padding: 0" }
+ %table.content{ border: "0", cellpadding: "0", cellspacing: "0" }
+ %tbody
+ = yield
+
+ = render_if_exists 'layouts/mailer/additional_text'
+
+ %tr.footer
+ %td{ style: "padding: 24px 0" }
+ %img{ alt: "GitLab", height: "33", width: "90", src: image_url('mailers/gitlab_footer_logo.gif') }
+ %p{ style: "color: #949ba5; max-width: 640px; margin: 0 auto; text-align: left; font-size: 12px;" }
+ GitLab is a complete DevOps platform, delivered as a single application, fundamentally changing the way
+ %br
+ Development, Security, and Ops teams collaborate.
+
+ = yield :additional_footer
+ %tr
+ %td.footer-message
+ = html_footer_message
diff --git a/app/views/notify/member_invited_email.html.haml b/app/views/notify/member_invited_email.html.haml
index ae3fecf404a..4fcd2936d25 100644
--- a/app/views/notify/member_invited_email.html.haml
+++ b/app/views/notify/member_invited_email.html.haml
@@ -10,7 +10,7 @@
#{member_source.model_name.singular} as #{content_tag :span, member.human_access, class: :highlight}.
%p
- = link_to 'Accept invitation', invite_url(@token)
+ = link_to 'Accept invitation', invite_url(@token, @invite_url_params)
or
= link_to 'decline', decline_invite_url(@token)
diff --git a/app/views/notify/member_invited_email.text.erb b/app/views/notify/member_invited_email.text.erb
index d944c3b4a50..e6e6a685f92 100644
--- a/app/views/notify/member_invited_email.text.erb
+++ b/app/views/notify/member_invited_email.text.erb
@@ -1,4 +1,4 @@
You have been invited <%= "by #{sanitize_name(member.created_by.name)} " if member.created_by %>to join the <%= member_source.human_name %> <%= member_source.model_name.singular %> as <%= member.human_access %>.
-Accept invitation: <%= invite_url(@token) %>
+Accept invitation: <%= invite_url(@token, @invite_url_params) %>
Decline invitation: <%= decline_invite_url(@token) %>
diff --git a/app/views/notify/member_invited_email_experiment.html.haml b/app/views/notify/member_invited_email_experiment.html.haml
new file mode 100644
index 00000000000..5cfb6acee05
--- /dev/null
+++ b/app/views/notify/member_invited_email_experiment.html.haml
@@ -0,0 +1,12 @@
+%tr
+ %td.text-content
+ %h2.invite-header
+ = s_('InviteEmail|You are invited!')
+ %p
+ - if member.created_by
+ = html_escape(s_("InviteEmail|%{inviter} invited you")) % { inviter: (link_to member.created_by.name, user_url(member.created_by)).html_safe }
+ = html_escape(s_("InviteEmail|to join the %{strong_start}%{project_or_group_name}%{strong_end}")) % { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe, project_or_group_name: member_source.human_name }
+ %br
+ = s_("InviteEmail|%{project_or_group} as a %{role}") % { project_or_group: member_source.model_name.singular, role: member.human_access.downcase }
+ %p.invite-actions
+ = link_to s_('InviteEmail|Join now'), invite_url(@token, @invite_url_params), class: 'invite-btn-join'
diff --git a/app/views/notify/member_invited_email_experiment.text.erb b/app/views/notify/member_invited_email_experiment.text.erb
new file mode 100644
index 00000000000..6843cea4df7
--- /dev/null
+++ b/app/views/notify/member_invited_email_experiment.text.erb
@@ -0,0 +1,10 @@
+<% project_and_role = s_('InviteEmail|to join the %{project_or_group_name} %{project_or_group} as a %{role}') \
+ % { project_or_group_name: member_source.human_name, project_or_group: member_source.model_name.singular, role: member.human_access.downcase } %>
+
+<% if member.created_by %>
+<%= s_('InviteEmail|%{inviter} invited you') % { inviter: sanitize_name(member.created_by.name) } %> <%= project_and_role %>
+<% else %>
+<%= s_('InviteEmail|You have been invited') %> <%= project_and_role %>
+<% end %>
+
+Join now: <%= invite_url(@token, @invite_url_params) %>
diff --git a/changelogs/unreleased/240874-expose-alert-via-issues-type.yml b/changelogs/unreleased/240874-expose-alert-via-issues-type.yml
new file mode 100644
index 00000000000..023e3483293
--- /dev/null
+++ b/changelogs/unreleased/240874-expose-alert-via-issues-type.yml
@@ -0,0 +1,5 @@
+---
+title: Add alert to Issue type in GraphQL
+merge_request: 40214
+author:
+type: added
diff --git a/changelogs/unreleased/backup_wrap_concurrency.yml b/changelogs/unreleased/backup_wrap_concurrency.yml
new file mode 100644
index 00000000000..28e2eaf483d
--- /dev/null
+++ b/changelogs/unreleased/backup_wrap_concurrency.yml
@@ -0,0 +1,5 @@
+---
+title: Fix race condition in concurrent backups
+merge_request: 39894
+author:
+type: fixed
diff --git a/changelogs/unreleased/bump-puma-to-4-3-5-gitlab-3.yml b/changelogs/unreleased/bump-puma-to-4-3-5-gitlab-3.yml
new file mode 100644
index 00000000000..781dfd423b6
--- /dev/null
+++ b/changelogs/unreleased/bump-puma-to-4-3-5-gitlab-3.yml
@@ -0,0 +1,5 @@
+---
+title: Update gitlab-puma to 4.3.5-gitlab-3
+merge_request: 40389
+author:
+type: changed
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 9e2aa602767..a372bfa9f5f 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -622,7 +622,7 @@ For more details see this [blog post](https://about.gitlab.com/blog/2020/08/03/h
GitLab Pages can use an API-based configuration. This replaces disk source configuration, which
was used prior to GitLab 13.0. Follow these steps to enable it:
-1. Add the following to your `/etc/gitlab/gitlab.erb` file:
+1. Add the following to your `/etc/gitlab/gitlab.rb` file:
```ruby
gitlab_pages['domain_config_source'] = "gitlab"
diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md
index fe2dad41066..9a8aa176e77 100644
--- a/doc/administration/reference_architectures/10k_users.md
+++ b/doc/administration/reference_architectures/10k_users.md
@@ -325,6 +325,9 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
1. Configure the GitLab application servers with the appropriate details.
This step is covered in [Configuring the GitLab Rails application](#configure-gitlab-rails).
+See [Configure GitLab using an external PostgreSQL service](../postgresql/external.md) for
+further configuration steps.
+
### Standalone PostgreSQL using Omnibus GitLab
The following IPs will be used as an example:
@@ -1997,7 +2000,7 @@ based on what features you intend to use:
1. Configure [object storage for packages](../packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. Configure [object storage for Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. Configure [object storage for Pseudonymizer](../pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
-1. Configure [object storage for autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
+1. Configure [object storage for autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
1. Configure [object storage for Terraform state files](../terraform_state.md#using-object-storage-core-only).
Using separate buckets for each data type is the recommended approach for GitLab.
diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md
index 1cfa2565893..adf4f7ea0e3 100644
--- a/doc/administration/reference_architectures/25k_users.md
+++ b/doc/administration/reference_architectures/25k_users.md
@@ -325,6 +325,9 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
1. Configure the GitLab application servers with the appropriate details.
This step is covered in [Configuring the GitLab Rails application](#configure-gitlab-rails).
+See [Configure GitLab using an external PostgreSQL service](../postgresql/external.md) for
+further configuration steps.
+
### Standalone PostgreSQL using Omnibus GitLab
The following IPs will be used as an example:
@@ -1997,7 +2000,7 @@ based on what features you intend to use:
1. Configure [object storage for packages](../packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. Configure [object storage for Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. Configure [object storage for Pseudonymizer](../pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
-1. Configure [object storage for autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
+1. Configure [object storage for autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
1. Configure [object storage for Terraform state files](../terraform_state.md#using-object-storage-core-only).
Using separate buckets for each data type is the recommended approach for GitLab.
diff --git a/doc/administration/reference_architectures/2k_users.md b/doc/administration/reference_architectures/2k_users.md
index a7feb78a365..23cc1352d11 100644
--- a/doc/administration/reference_architectures/2k_users.md
+++ b/doc/administration/reference_architectures/2k_users.md
@@ -193,6 +193,9 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
1. Configure the GitLab application servers with the appropriate details.
This step is covered in [Configuring the GitLab Rails application](#configure-gitlab-rails).
+See [Configure GitLab using an external PostgreSQL service](../postgresql/external.md) for
+further configuration steps.
+
### Standalone PostgreSQL using Omnibus GitLab
1. SSH into the PostgreSQL server.
@@ -825,7 +828,7 @@ on the features you intend to use:
1. [Object storage for packages](../packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. [Object storage for Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. [Object storage for Pseudonymizer](../pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
-1. [Object storage for autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional, for improved performance).
+1. [Object storage for autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional, for improved performance).
1. [Object storage for Terraform state files](../terraform_state.md#using-object-storage-core-only).
Using separate buckets for each data type is the recommended approach for GitLab.
diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md
index 2f88413de6f..2a9590d1908 100644
--- a/doc/administration/reference_architectures/3k_users.md
+++ b/doc/administration/reference_architectures/3k_users.md
@@ -600,6 +600,9 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
1. Configure the GitLab application servers with the appropriate details.
This step is covered in [Configuring the GitLab Rails application](#configure-gitlab-rails).
+See [Configure GitLab using an external PostgreSQL service](../postgresql/external.md) for
+further configuration steps.
+
### Standalone PostgreSQL using Omnibus GitLab
The following IPs will be used as an example:
@@ -1723,7 +1726,7 @@ based on what features you intend to use:
1. Configure [object storage for packages](../packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. Configure [object storage for Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. Configure [object storage for Pseudonymizer](../pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
-1. Configure [object storage for autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
+1. Configure [object storage for autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
1. Configure [object storage for Terraform state files](../terraform_state.md#using-object-storage-core-only).
Using separate buckets for each data type is the recommended approach for GitLab.
diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md
index 565845b4bf5..6fe1174dac0 100644
--- a/doc/administration/reference_architectures/50k_users.md
+++ b/doc/administration/reference_architectures/50k_users.md
@@ -325,6 +325,9 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
1. Configure the GitLab application servers with the appropriate details.
This step is covered in [Configuring the GitLab Rails application](#configure-gitlab-rails).
+See [Configure GitLab using an external PostgreSQL service](../postgresql/external.md) for
+further configuration steps.
+
### Standalone PostgreSQL using Omnibus GitLab
The following IPs will be used as an example:
@@ -1997,7 +2000,7 @@ based on what features you intend to use:
1. Configure [object storage for packages](../packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. Configure [object storage for Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. Configure [object storage for Pseudonymizer](../pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
-1. Configure [object storage for autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
+1. Configure [object storage for autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
1. Configure [object storage for Terraform state files](../terraform_state.md#using-object-storage-core-only).
Using separate buckets for each data type is the recommended approach for GitLab.
diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md
index 14685ffa53d..f485b7d35e8 100644
--- a/doc/administration/reference_architectures/5k_users.md
+++ b/doc/administration/reference_architectures/5k_users.md
@@ -600,6 +600,9 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
1. Configure the GitLab application servers with the appropriate details.
This step is covered in [Configuring the GitLab Rails application](#configure-gitlab-rails).
+See [Configure GitLab using an external PostgreSQL service](../postgresql/external.md) for
+further configuration steps.
+
### Standalone PostgreSQL using Omnibus GitLab
The following IPs will be used as an example:
@@ -1722,7 +1725,7 @@ based on what features you intend to use:
1. Configure [object storage for packages](../packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. Configure [object storage for Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. Configure [object storage for Pseudonymizer](../pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
-1. Configure [object storage for autoscale Runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
+1. Configure [object storage for autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional - for improved performance).
1. Configure [object storage for Terraform state files](../terraform_state.md#using-object-storage-core-only).
Using separate buckets for each data type is the recommended approach for GitLab.
diff --git a/doc/administration/troubleshooting/sidekiq.md b/doc/administration/troubleshooting/sidekiq.md
index 9125ddf545f..c628ed837eb 100644
--- a/doc/administration/troubleshooting/sidekiq.md
+++ b/doc/administration/troubleshooting/sidekiq.md
@@ -212,7 +212,7 @@ the query details.
## Managing Sidekiq queues
It is possible to use [Sidekiq API](https://github.com/mperham/sidekiq/wiki/API)
-to perform a number of troubleshooting on Sidekiq.
+to perform a number of troubleshooting steps on Sidekiq.
These are the administrative commands and it should only be used if currently
admin interface is not suitable due to scale of installation.
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index eb781fc082b..4d244e8b6f6 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -5153,6 +5153,11 @@ Relationship between an epic and an issue
"""
type EpicIssue implements Noteable {
"""
+ Alert associated to this issue
+ """
+ alertManagementAlert: AlertManagementAlert
+
+ """
Assignees of the issue
"""
assignees(
@@ -6872,6 +6877,11 @@ enum IssuableState {
type Issue implements Noteable {
"""
+ Alert associated to this issue
+ """
+ alertManagementAlert: AlertManagementAlert
+
+ """
Assignees of the issue
"""
assignees(
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index fa4b5620b86..479ac8981fe 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -14375,6 +14375,20 @@
"description": "Relationship between an epic and an issue",
"fields": [
{
+ "name": "alertManagementAlert",
+ "description": "Alert associated to this issue",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "AlertManagementAlert",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "assignees",
"description": "Assignees of the issue",
"args": [
@@ -18952,6 +18966,20 @@
"description": null,
"fields": [
{
+ "name": "alertManagementAlert",
+ "description": "Alert associated to this issue",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "AlertManagementAlert",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "assignees",
"description": "Assignees of the issue",
"args": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 635d1a6fe1e..e0c8e903aba 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -866,6 +866,7 @@ Relationship between an epic and an issue
| Name | Type | Description |
| --- | ---- | ---------- |
+| `alertManagementAlert` | AlertManagementAlert | Alert associated to this issue |
| `author` | User! | User that created the issue |
| `blocked` | Boolean! | Indicates the issue is blocked |
| `closedAt` | Time | Timestamp of when the issue was closed |
@@ -1038,6 +1039,7 @@ Represents a Group Membership
| Name | Type | Description |
| --- | ---- | ---------- |
+| `alertManagementAlert` | AlertManagementAlert | Alert associated to this issue |
| `author` | User! | User that created the issue |
| `blocked` | Boolean! | Indicates the issue is blocked |
| `closedAt` | Time | Timestamp of when the issue was closed |
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 2159f7a9ed5..283f88ec0e1 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -361,9 +361,10 @@ When ready to merge:
- If the **latest [Pipeline for Merged Results](../ci/merge_request_pipelines/pipelines_for_merged_results/#pipelines-for-merged-results-premium)** finished less than 2 hours ago, you
might merge without starting a new pipeline as the merge request is close
enough to `master`.
- - If the **merge request is from a fork**, we can't use [Pipelines for Merged Results](../ci/merge_request_pipelines/pipelines_for_merged_results/index.md#prerequisites), therefore, they're more prone to breaking `master`.
- Check how far behind `master` the source branch is. If it's more than 100 commits behind, ask the author to
- rebase it before merging.
+ - If the **merge request is from a fork**, we can use [Pipelines for Merged Results from a forked project](../ci/merge_request_pipelines/index.md#run-pipelines-in-the-parent-project-for-merge-requests-from-a-forked-project-starter) with caution.
+ Before triggering the pipeline, review all changes for **malicious code**.
+ If you cannot trigger the pipeline, review the status of the fork relative to `master`.
+ If it's more than 100 commits behind, ask the author to rebase it before merging.
- If [master is broken](https://about.gitlab.com/handbook/engineering/workflow/#broken-master),
in addition to the two above rules, check that any failure also happens
in `master` and post a link to the ~"master:broken" issue before clicking the
diff --git a/doc/install/docker.md b/doc/install/docker.md
index e0cef71a4d8..c2d7655d526 100644
--- a/doc/install/docker.md
+++ b/doc/install/docker.md
@@ -6,18 +6,10 @@ type: index
[Docker](https://www.docker.com) and container technology have been revolutionizing the software world for the past few years. They combine the performance and efficiency of native execution with the abstraction, security, and immutability of virtualization.
-GitLab provides official Docker images allowing you to easily take advantage of the benefits of containerization while operating your GitLab instance.
+GitLab provides official Docker images allowing you to easily take advantage of the benefits of containerization while operating your GitLab instance. A [complete usage guide](https://docs.gitlab.com/omnibus/docker/) for these images is available, as well as the [Dockerfile used for building the images](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker).
-## Omnibus GitLab based images
-
-GitLab maintains a set of [official Docker images](https://hub.docker.com/u/gitlab) based on our [Omnibus GitLab package](https://docs.gitlab.com/omnibus/README.html). These images include:
-
-- [GitLab Community Edition](https://hub.docker.com/r/gitlab/gitlab-ce/)
-- [GitLab Enterprise Edition](https://hub.docker.com/r/gitlab/gitlab-ee/)
-- [GitLab Runner](https://hub.docker.com/r/gitlab/gitlab-runner/)
-
-A [complete usage guide](https://docs.gitlab.com/omnibus/docker/) to these images is available, as well as the [Dockerfile used for building the images](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker).
+There's also a [Docker image for GitLab Runner](https://docs.gitlab.com/runner/install/docker.html).
## Cloud native images
-GitLab is also working towards a [cloud native set of containers](https://docs.gitlab.com/charts/), with a single image for each component service. We intend for these images to eventually replace the [Omnibus GitLab based images](#omnibus-gitlab-based-images).
+GitLab is also working towards a [cloud native set of containers](https://docs.gitlab.com/charts/), with a single image for each component service.
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 67b256cc944..0408a03a630 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -1,43 +1,85 @@
+---
+type: reference
+stage: Enablement
+group: Global Search
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Elasticsearch integration **(STARTER ONLY)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109 "Elasticsearch Merge Request") in GitLab [Starter](https://about.gitlab.com/pricing/) 8.4.
> - Support for [Amazon Elasticsearch](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html) was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1305) in GitLab [Starter](https://about.gitlab.com/pricing/) 9.0.
-This document describes how to set up Elasticsearch with GitLab. Once enabled,
-you'll have the benefit of fast search response times and the advantage of two
-special searches:
+This document describes how to set up Elasticsearch with GitLab. After
+Elasticsearch is enabled, you'll have the benefit of fast search response times
+and the advantage of the following special searches:
- [Advanced Global Search](../user/search/advanced_global_search.md)
- [Advanced Syntax Search](../user/search/advanced_search_syntax.md)
-## Version Requirements
+## Version requirements
+
+<!-- Remember to update ee/lib/system_check/app/elasticsearch_check.rb if this changes -->
+
+| GitLab version | Elasticsearch version |
+|---------------------------------------------|-------------------------------|
+| GitLab Enterprise Edition 12.7 or greater | Elasticsearch 6.x through 7.x |
+| GitLab Enterprise Edition 11.5 through 12.6 | Elasticsearch 5.6 through 6.x |
+| GitLab Enterprise Edition 9.0 through 11.4 | Elasticsearch 5.1 through 5.5 |
+| GitLab Enterprise Edition 8.4 through 8.17 | Elasticsearch 2.4 with [Delete By Query Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/2.4/plugins-delete-by-query.html) installed |
+
+## System requirements
+
+Elasticsearch requires additional resources in excess of those documented in the
+[GitLab system requirements](../install/requirements.md).
+
+The amount of resources (memory, CPU, storage) will vary greatly, based on the
+amount of data being indexed into the Elasticsearch cluster. According to
+[Elasticsearch official guidelines](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html#_memory),
+each node should have:
+
+- [Memory](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html#_memory): 8 GiB (minimum).
+- [CPU](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html#_cpus): Modern processor with multiple cores.
+- [Storage](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html#_disks): Use SSD storage. You will need enough storage for 50% of the total size of your Git repositories.
+
+A few notes on CPU and storage:
+
+- CPU requirements for Elasticsearch tend to be minimal. There are specific
+ scenarios where this isn't true, but GitLab.com isn't using Elasticsearch in
+ an exceptionally CPU-heavy way. More cores will be more performant than faster
+ CPUs. Extra concurrency from multiple cores will far outweigh a slightly
+ faster clock speed in Elasticsearch.
-<!-- Please remember to update ee/lib/system_check/app/elasticsearch_check.rb if this changes -->
+- Storage requirements for Elasticsearch are important, especially for
+ indexing-heavy clusters. When possible use SSDs, whose speed is far superior
+ to any spinning media for Elasticsearch. In testing, nodes that use SSD storage
+ see boosts in both query and indexing performance.
-| GitLab version | Elasticsearch version |
-| -------------- | --------------------- |
-| GitLab Enterprise Edition 8.4 - 8.17 | Elasticsearch 2.4 with [Delete By Query Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/2.4/plugins-delete-by-query.html) installed |
-| GitLab Enterprise Edition 9.0 - 11.4 | Elasticsearch 5.1 - 5.5 |
-| GitLab Enterprise Edition 11.5 - 12.6 | Elasticsearch 5.6 - 6.x |
-| GitLab Enterprise Edition 12.7+ | Elasticsearch 6.x - 7.x |
+Keep in mind, these are **minimum requirements** for Elasticsearch.
+Heavily-utilized Elasticsearch clusters will likely require considerably more
+resources.
## Installing Elasticsearch
-Elasticsearch is _not_ included in the Omnibus packages or when you install from source. You must
-[install it separately](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/install-elasticsearch.html "Elasticsearch 7.x installation documentation"). Be sure to select your version.
-Providing detailed information on installing Elasticsearch is out of the scope
-of this document.
+Elasticsearch is *not* included in the Omnibus packages or when you install from
+source. You must [install it separately](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/install-elasticsearch.html "Elasticsearch 7.x installation documentation").
+Be sure to select your version. Providing detailed information on installing
+Elasticsearch is out of the scope of this document.
NOTE: **Note:**
Elasticsearch should be installed on a separate server, whether you install
-it yourself or use a cloud hosted offering like Elastic's [Elasticsearch Service](https://www.elastic.co/elasticsearch/service) (available on AWS, GCP, or Azure) or the
-[Amazon Elasticsearch](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html) service. Running Elasticsearch on the same server as GitLab is not recommended
-and will likely cause a degradation in GitLab instance performance.
+it yourself or use a cloud hosted offering like Elastic's [Elasticsearch Service](https://www.elastic.co/elasticsearch/service)
+(available on AWS, GCP, or Azure) or the [Amazon Elasticsearch](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html)
+service. Running Elasticsearch on the same server as GitLab is not recommended
+and can cause a degradation in GitLab instance performance.
NOTE: **Note:**
-**For a single node Elasticsearch cluster the functional cluster health status will be yellow** (will never be green) because the primary shard is allocated but replicas can not be as there is no other node to which Elasticsearch can assign a replica.
+**For a single node Elasticsearch cluster the functional cluster health status
+will be yellow** (will never be green) because the primary shard is allocated but
+replicas can not be as there is no other node to which Elasticsearch can assign a
+replica.
-Once the data is added to the database or repository and [Elasticsearch is
+After the data is added to the database or repository and [Elasticsearch is
enabled in the Admin Area](#enabling-elasticsearch) the search index will be
updated automatically.
@@ -47,12 +89,13 @@ For indexing Git repository data, GitLab uses an [indexer written in Go](https:/
The way you install the Go indexer depends on your version of GitLab:
-- For Omnibus GitLab 11.8 and above, see [Omnibus GitLab](#omnibus-gitlab).
-- For installations from source or older versions of Omnibus GitLab, install the indexer [From Source](#from-source).
+- For Omnibus GitLab 11.8 or greater, see [Omnibus GitLab](#omnibus-gitlab).
+- For installations from source or older versions of Omnibus GitLab,
+ [install the indexer from source](#from-source).
### Omnibus GitLab
-Since GitLab 11.8 the Go indexer is included in Omnibus GitLab.
+Starting with GitLab 11.8, the Go indexer is included in Omnibus GitLab.
The former Ruby-based indexer was removed in [GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/-/issues/6481).
### From source
@@ -80,7 +123,7 @@ To install on CentOS or RHEL, run:
sudo yum install libicu-devel
```
-##### Mac OSX
+#### Mac OSX
To install on macOS, run:
@@ -112,60 +155,81 @@ Example:
PREFIX=/usr sudo -E make install
```
-Once installed, enable it under your instance's Elasticsearch settings explained [below](#enabling-elasticsearch).
+After installation, be sure to [enable Elasticsearch](#enabling-elasticsearch).
-## System Requirements
+## Enabling Elasticsearch
-Elasticsearch requires additional resources in excess of those documented in the
-[GitLab system requirements](../install/requirements.md).
+NOTE: **Note:**
+For large GitLab instances you can follow the instructions for [Indexing large
+instances](#indexing-large-instances) below.
-The amount of resources (memory, CPU, storage) will vary greatly, based on the amount of data being indexed into the Elasticsearch cluster. According to [Elasticsearch official guidelines](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html#_memory), each node should have:
+In order to enable Elasticsearch, you need to have admin access in GitLab.
-- [RAM](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html#_disks): **8 GiB as the bare minimum**
-- [CPU](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html#_cpus): Modern processor with multiple cores
-- [Storage](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html#_disks): Use SSD storage. As a guide you will need enough storage for 50% of the total size of your Git repositories.
+1. Navigate to **Admin Area** (wrench icon), then **Settings > Integrations**
+ and expand the **Elasticsearch** section.
+1. Configure the [Elasticsearch settings](#elasticsearch-configuration) for
+ your Elasticsearch cluster. Do not enable **Elasticsearch indexing** or
+ **Search with Elasticsearch** yet.
+1. Click **Save changes** for the changes to take effect.
+1. Before enabling Elasticsearch indexing you need to create an index by
+ running the Rake task:
-A few notes on CPU and storage:
+ ```shell
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:create_empty_index
-- CPU requirements for Elasticsearch tend to be light. There are specific scenarios where this isn't true, but GitLab.com isn't using Elasticsearch in an exceptionally CPU-heavy way. More cores will be more performant than faster CPUs. Extra concurrency from multiple cores will far outweigh a slightly faster clock speed in Elasticsearch.
+ # Installations from source
+ bundle exec rake gitlab:elastic:create_empty_index RAILS_ENV=production
+ ```
-- Storage requirements for Elasticsearch are important, especially for indexing-heavy clusters. When possible, use SSDs, Their speed is far superior to any spinning media for Elasticsearch. In testing, nodes that use SSD storage see boosts in both query and indexing performance.
+1. Now enable `Elasticsearch indexing` in **Admin Area > Settings >
+ Integrations > Elasticsearch** and click **Save changes**.
+1. Click **Index all projects**.
+1. Click **Check progress** in the confirmation message to see the status of
+ the background jobs.
+1. Personal snippets need to be indexed using another Rake task:
-Keep in mind, these are **minimum requirements** for Elasticsearch. Heavily-utilized Elasticsearch clusters will likely require considerably more resources.
+ ```shell
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:index_snippets
-## Enabling Elasticsearch
+ # Installations from source
+ bundle exec rake gitlab:elastic:index_snippets RAILS_ENV=production
+ ```
-In order to enable Elasticsearch, you need to have admin access. Navigate to
-**Admin Area** (wrench icon), then **Settings > Integrations** and expand the **Elasticsearch** section.
+1. After the indexing has completed, enable **Search with Elasticsearch** in
+ **Admin Area > Settings > Integrations > Elasticsearch** and click **Save
+ changes**.
-Click **Save changes** for the changes to take effect.
+### Elasticsearch configuration
The following Elasticsearch settings are available:
| Parameter | Description |
-| ----------------------------------------------------- | ----------- |
-| `Elasticsearch indexing` | Enables/disables Elasticsearch indexing. You may want to enable indexing but disable search in order to give the index time to be fully completed, for example. Also, keep in mind that this option doesn't have any impact on existing data, this only enables/disables background indexer which tracks data changes. So by enabling this you will not get your existing data indexed, use special Rake task for that as explained in [Adding GitLab's data to the Elasticsearch index](#adding-gitlabs-data-to-the-elasticsearch-index). |
-| `Elasticsearch pause indexing` | Enables/disables temporary indexing pause. This is useful for cluster migration/reindexing. All changes are still tracked, but they are not committed to the Elasticsearch index until unpaused. |
-| `Search with Elasticsearch enabled` | Enables/disables using Elasticsearch in search. |
+|-------------------------------------------------------|-------------|
+| `Elasticsearch indexing` | Enables or disables Elasticsearch indexing. You may want to enable indexing but disable search in order to give the index time to be fully completed, for example. Also, keep in mind that this option doesn't have any impact on existing data, this only enables/disables the background indexer which tracks data changes and ensures new data is indexed. |
+| `Elasticsearch pause indexing` | Enables or disables temporary indexing pause. This is useful for cluster migration/reindexing. All changes are still tracked, but they are not committed to the Elasticsearch index until unpaused. |
+| `Search with Elasticsearch enabled` | Enables or disables using Elasticsearch in search. |
| `URL` | The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., `http://host1, https://host2:9200`). If your Elasticsearch instance is password protected, pass the `username:password` in the URL (e.g., `http://<username>:<password>@<elastic_host>:9200/`). |
| `Number of Elasticsearch shards` | Elasticsearch indexes are split into multiple shards for performance reasons. In general, larger indexes need to have more shards. Changes to this value do not take effect until the index is recreated. You can read more about tradeoffs in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/scalability.html). |
| `Number of Elasticsearch replicas` | Each Elasticsearch shard can have a number of replicas. These are a complete copy of the shard, and can provide increased query performance or resilience against hardware failure. Increasing this value will greatly increase total disk space required by the index. |
| `Limit namespaces and projects that can be indexed` | Enabling this will allow you to select namespaces and projects to index. All other namespaces and projects will use database search instead. Please note that if you enable this option but do not select any namespaces or projects, none will be indexed. [Read more below](#limiting-namespaces-and-projects).
| `Using AWS hosted Elasticsearch with IAM credentials` | Sign your Elasticsearch requests using [AWS IAM authorization](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html), [AWS EC2 Instance Profile Credentials](https://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli), or [AWS ECS Tasks Credentials](https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html). The policies must be configured to allow `es:*` actions. |
-| `AWS Region` | The AWS region your Elasticsearch service is located in. |
+| `AWS Region` | The AWS region in which your Elasticsearch service is located. |
| `AWS Access Key` | The AWS access key. |
| `AWS Secret Access Key` | The AWS secret access key. |
| `Maximum file size indexed` | See [the explanation in instance limits.](../administration/instance_limits.md#maximum-file-size-indexed). |
| `Maximum field length` | See [the explanation in instance limits.](../administration/instance_limits.md#maximum-field-length). |
| `Maximum bulk request size (MiB)` | The Maximum Bulk Request size is used by GitLab's Golang-based indexer processes and indicates how much data it ought to collect (and store in memory) in a given indexing process before submitting the payload to Elasticsearch’s Bulk API. This setting should be used with the Bulk request concurrency setting (see below) and needs to accommodate the resource constraints of both the Elasticsearch host(s) and the host(s) running GitLab's Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
-| `Bulk request concurrency` | The Bulk request concurrency indicates how many of GitLab's Golang-based indexer processes (or threads) can run in parallel to collect data to subsequently submit to Elasticsearch’s Bulk API. This increases indexing performance, but fills the Elasticsearch bulk requests queue faster. This setting should be used together with the Maximum bulk request size setting (see above) and needs to accommodate the resource constraints of both the Elasticsearch host(s) and the host(s) running GitLab's Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
+| `Bulk request concurrency` | The Bulk request concurrency indicates how many of GitLab's Golang-based indexer processes (or threads) can run in parallel to collect data to subsequently submit to Elasticsearch’s Bulk API. This increases indexing performance, but fills the Elasticsearch bulk requests queue faster. This setting should be used together with the Maximum bulk request size setting (see above) and needs to accommodate the resource constraints of both the Elasticsearch host(s) and the host(s) running GitLab's Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
### Limiting namespaces and projects
-If you select `Limit namespaces and projects that can be indexed`, more options will become available
+If you select `Limit namespaces and projects that can be indexed`, more options will become available.
+
![limit namespaces and projects options](img/limit_namespaces_projects_options.png)
-You can select namespaces and projects to index exclusively. Please note that if the namespace is a group it will include
+You can select namespaces and projects to index exclusively. Note that if the namespace is a group it will include
any sub-groups and projects belonging to those sub-groups to be indexed as well.
Elasticsearch only provides cross-group code/commit search (global) if all name-spaces are indexed. In this particular scenario where only a subset of namespaces are indexed, a global search will not provide a code or commit scope. This will be possible only in the scope of an indexed namespace. Currently there is no way to code/commit search in multiple indexed namespaces (when only a subset of namespaces has been indexed). For example if two groups are indexed, there is no way to run a single code search on both. You can only run a code search on the first group and then on the second.
@@ -201,73 +265,13 @@ To disable the Elasticsearch integration:
bundle exec rake gitlab:elastic:delete_index RAILS_ENV=production
```
-## Adding GitLab's data to the Elasticsearch index
-
-While Elasticsearch indexing is enabled, new changes in your GitLab instance will be automatically indexed as they happen.
-To backfill existing data, you can use one of the methods below to index it in background jobs.
-
-### Indexing through the administration UI
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/15390) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.3.
-
-To index via the Admin Area:
-
-1. [Configure your Elasticsearch host and port](#enabling-elasticsearch).
-1. Create empty indexes:
-
- ```shell
- # Omnibus installations
- sudo gitlab-rake gitlab:elastic:create_empty_index
-
- # Installations from source
- bundle exec rake gitlab:elastic:create_empty_index RAILS_ENV=production
- ```
-
-1. [Enable **Elasticsearch indexing**](#enabling-elasticsearch).
-1. Click **Index all projects** in **Admin Area > Settings > Integrations > Elasticsearch**.
-1. Click **Check progress** in the confirmation message to see the status of the background jobs.
-1. Personal snippets need to be indexed manually:
-
- ```shell
- # Omnibus installations
- sudo gitlab-rake gitlab:elastic:index_snippets
-
- # Installations from source
- bundle exec rake gitlab:elastic:index_snippets RAILS_ENV=production
- ```
-
-1. After the indexing has completed, enable [**Search with Elasticsearch**](#enabling-elasticsearch).
-
-### Indexing through Rake tasks
-
-Indexing can be performed using Rake tasks.
-
-#### Indexing small instances
+### Indexing large instances
CAUTION: **Warning:**
-This will delete your existing indexes.
-
-If the database size is less than 500 MiB, and the size of all hosted repos is less than 5 GiB:
-
-1. [Configure your Elasticsearch host and port](#enabling-elasticsearch).
-1. Index your data:
-
- ```shell
- # Omnibus installations
- sudo gitlab-rake gitlab:elastic:index
-
- # Installations from source
- bundle exec rake gitlab:elastic:index RAILS_ENV=production
- ```
-
-1. After the indexing has completed, enable [**Search with Elasticsearch**](#enabling-elasticsearch).
-
-#### Indexing large instances
-
-CAUTION: **Warning:**
-Performing asynchronous indexing will generate a lot of Sidekiq jobs.
-Make sure to prepare for this task by having a [Scalable and Highly Available Setup](README.md)
-or creating [extra Sidekiq processes](../administration/operations/extra_sidekiq_processes.md)
+Indexing a large instance will generate a lot of Sidekiq jobs.
+Make sure to prepare for this task by having a [Scalable and Highly Available
+Setup](../administration/reference_architectures/index.md) or creating [extra
+Sidekiq processes](../administration/operations/extra_sidekiq_processes.md)
1. [Configure your Elasticsearch host and port](#enabling-elasticsearch).
1. Create empty indexes:
@@ -419,32 +423,36 @@ or creating [extra Sidekiq processes](../administration/operations/extra_sidekiq
1. After the indexing has completed, enable [**Search with Elasticsearch**](#enabling-elasticsearch).
-### Indexing limitations
-
-For repository and snippet files, GitLab will only index up to 1 MiB of content, in order to avoid indexing timeouts.
-
## Zero downtime reindexing
-The idea behind this reindexing method is to leverage Elasticsearch index alias feature to atomically swap between two indices.
-We will refer to each index as `primary` (online and used by GitLab for read/writes) and `secondary` (offline, for reindexing purpose).
+The idea behind this reindexing method is to leverage Elasticsearch index alias
+feature to atomically swap between two indices. We'll refer to each index as
+`primary` (online and used by GitLab for read/writes) and `secondary`
+(offline, for reindexing purpose).
-Instead of connecting directly to the `primary` index, we'll setup an index alias such as we can change the underlying index at will.
+Instead of connecting directly to the `primary` index, we'll setup an index
+alias such as we can change the underlying index at will.
NOTE: **Note:**
-Any index attached to the production alias is deemed a `primary` and will end up being used by the GitLab Elasticsearch integration.
+Any index attached to the production alias is deemed a `primary` and will be
+used by the GitLab Elasticsearch integration.
### Pause the indexing
-Under **Admin Area > Integration > Elasticsearch**, check the **Pause Elasticsearch Indexing** setting and save.
+In the **Admin Area > Integration > Elasticsearch** section, select the
+**Pause Elasticsearch Indexing** setting, and then save your change.
-With this, all updates that should happen on your Elasticsearch index will be buffered and caught up once unpaused.
+With this, all updates that should happen on your Elasticsearch index will be
+buffered and caught up once unpaused.
### Setup
TIP: **Tip:**
-If your index has been created with GitLab v13.0+ you can skip directly to [trigger the reindex](#trigger-the-reindex-via-the-elasticsearch-administration).
+If your index was created with GitLab 13.0 or greater, you can directly
+[trigger the reindex](#trigger-the-reindex-via-the-elasticsearch-administration).
-This process involves multiple shell commands and curl invocations, so a good initial setup will help down the road:
+This process involves several shell commands and curl invocations, so a good
+initial setup will help for later:
```shell
# You can find this value under Admin Area > Integration > Elasticsearch > URL
@@ -456,10 +464,12 @@ export SECONDARY_INDEX="gitlab-production-$(date +%s)"
### Reclaiming the `gitlab-production` index name
CAUTION: **Caution:**
-It is highly recommended that you take a snapshot of your cluster to make sure there is a recovery path if anything goes wrong.
+It is highly recommended that you take a snapshot of your cluster to ensure
+there is a recovery path if anything goes wrong.
NOTE: **Note:**
-Due to a technical limitation, there will be a slight downtime because of the fact that we need to reclaim the current `primary` index to be used as the alias.
+Due to a technical limitation, there will be a slight downtime because of the
+fact that we need to reclaim the current `primary` index to be used as the alias.
To reclaim the `gitlab-production` index name, you need to first create a `secondary` index and then trigger the re-index from `primary`.
@@ -477,7 +487,8 @@ sudo SKIP_ALIAS=1 gitlab-rake "gitlab:elastic:create_empty_index[$SECONDARY_INDE
SKIP_ALIAS=1 bundle exec rake "gitlab:elastic:create_empty_index[$SECONDARY_INDEX]"
```
-The index should be created successfully, with the latest index options and mappings.
+The index should be created successfully, with the latest index options and
+mappings.
#### Trigger the re-index from `primary`
@@ -498,9 +509,9 @@ To trigger the re-index from `primary` index:
{"task":"3qw_Tr0YQLq7PF16Xek8YA:1012"}
```
- Note the `task` value here as it will be useful to follow the reindex progress.
+ Note the `task` value, as it will be useful to follow the reindex progress.
-1. Wait for the reindex process to complete, by checking the `completed` value.
+1. Wait for the reindex process to complete by checking the `completed` value.
Using the `task` value form the previous step:
```shell
@@ -514,10 +525,10 @@ To trigger the re-index from `primary` index:
{"completed":false, …}
```
- Once the returned value is `true`, you may continue to the next step.
+ After the returned value is `true`, continue to the next step.
-1. Make sure that the secondary index has data in it. You can use the Elasticsearch
- API to look for the index size and compare our two indices:
+1. Ensure that the secondary index has data in it. You can use the
+ Elasticsearch API to look for the index size and compare our two indices:
```shell
curl $CLUSTER_URL/$PRIMARY_INDEX/_count => 123123
@@ -527,7 +538,8 @@ To trigger the re-index from `primary` index:
TIP: **Tip:**
Comparing the document count is more accurate than using the index size, as improvements to the storage might cause the new index to be smaller than the original one.
-1. Once you are confident your `secondary` index is valid, you can process to the creation of the alias.
+1. After you are confident your `secondary` index is valid, you can process to
+ the creation of the alias.
```shell
# Delete the original index
@@ -567,7 +579,7 @@ Rake tasks are available to:
- [Build and install](#building-and-installing) the indexer.
- Delete indexes when [disabling Elasticsearch](#disabling-elasticsearch).
-- [Add GitLab data](#adding-gitlabs-data-to-the-elasticsearch-index) to an index.
+- Add GitLab data to an index.
The following are some available Rake tasks:
@@ -701,11 +713,11 @@ Here are some common pitfalls and how to overcome them:
We continuously make updates to our indexing strategies and aim to support
newer versions of Elasticsearch. When indexing changes are made, it may
- be necessary for you to [reindex](#adding-gitlabs-data-to-the-elasticsearch-index) after updating GitLab.
+ be necessary for you to [reindex](#zero-downtime-reindexing) after updating GitLab.
- **I indexed all the repositories but I can't find anything**
- Make sure you indexed all the database data [as stated above](#adding-gitlabs-data-to-the-elasticsearch-index).
+ 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.
@@ -801,7 +813,7 @@ Here are some common pitfalls and how to overcome them:
```
You probably have not used either `http://` or `https://` as part of your value in the **"URL"** field of the Elasticsearch Integration Menu. Please make sure you are using either `http://` or `https://` in this field as the [Elasticsearch client for Go](https://github.com/olivere/elastic) that we are using [needs the prefix for the URL to be accepted as valid](https://github.com/olivere/elastic/commit/a80af35aa41856dc2c986204e2b64eab81ccac3a).
- Once you have corrected the formatting of the URL, delete the index (via the [dedicated Rake task](#gitlab-elasticsearch-rake-tasks)) and [reindex the content of your instance](#adding-gitlabs-data-to-the-elasticsearch-index).
+ Once you have corrected the formatting of the URL, delete the index (via the [dedicated Rake task](#gitlab-elasticsearch-rake-tasks)) and [reindex the content of your instance](#enabling-elasticsearch).
### Low-level troubleshooting
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 1daa29f00ef..33fa36e6497 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -26,13 +26,17 @@ module Backup
threads = Gitlab.config.repositories.storages.keys.map do |storage|
Thread.new do
- dump_storage(storage, semaphore, max_storage_concurrency: max_storage_concurrency)
- rescue => e
- errors << e
+ Rails.application.executor.wrap do
+ dump_storage(storage, semaphore, max_storage_concurrency: max_storage_concurrency)
+ rescue => e
+ errors << e
+ end
end
end
- threads.each(&:join)
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ threads.each(&:join)
+ end
raise errors.pop unless errors.empty?
end
@@ -155,16 +159,18 @@ module Backup
threads = Array.new(max_storage_concurrency) do
Thread.new do
- while project = queue.pop
- semaphore.acquire
-
- begin
- dump_project(project)
- rescue => e
- errors << e
- break
- ensure
- semaphore.release
+ Rails.application.executor.wrap do
+ while project = queue.pop
+ semaphore.acquire
+
+ begin
+ dump_project(project)
+ rescue => e
+ errors << e
+ break
+ ensure
+ semaphore.release
+ end
end
end
end
@@ -177,7 +183,9 @@ module Backup
end
queue.close
- threads.each(&:join)
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ threads.each(&:join)
+ end
raise errors.pop unless errors.empty?
end
diff --git a/lib/gitlab/experimentation.rb b/lib/gitlab/experimentation.rb
index 47be22f35ec..0936cc17578 100644
--- a/lib/gitlab/experimentation.rb
+++ b/lib/gitlab/experimentation.rb
@@ -62,6 +62,9 @@ module Gitlab
},
customize_homepage: {
tracking_category: 'Growth::Expansion::Experiment::CustomizeHomepage'
+ },
+ invite_email: {
+ tracking_category: 'Growth::Acquisition::Experiment::InviteEmail'
}
}.freeze
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f5bb70a8ff8..24b6ccc509b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -13417,6 +13417,27 @@ msgstr ""
msgid "Invite teammates (optional)"
msgstr ""
+msgid "InviteEmail|%{inviter} invited you"
+msgstr ""
+
+msgid "InviteEmail|%{project_or_group} as a %{role}"
+msgstr ""
+
+msgid "InviteEmail|Join now"
+msgstr ""
+
+msgid "InviteEmail|You are invited!"
+msgstr ""
+
+msgid "InviteEmail|You have been invited"
+msgstr ""
+
+msgid "InviteEmail|to join the %{project_or_group_name} %{project_or_group} as a %{role}"
+msgstr ""
+
+msgid "InviteEmail|to join the %{strong_start}%{project_or_group_name}%{strong_end}"
+msgstr ""
+
msgid "Invited"
msgstr ""
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index adb496e9ef0..2bd0c6ae00e 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -133,7 +133,7 @@ module QA
Capybara::Screenshot.append_timestamp = false
Capybara::Screenshot.register_filename_prefix_formatter(:rspec) do |example|
- ::File.join(QA::Runtime::Namespace.name, example.full_description.downcase.parameterize(separator: "_")[0..99])
+ ::File.join(QA::Runtime::Namespace.name(reset_cache: false), example.full_description.downcase.parameterize(separator: "_")[0..99])
end
Capybara.configure do |config|
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index cbfce95d409..ea2ad59c1dd 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -268,6 +268,10 @@ module QA
ENV['JIRA_HOSTNAME']
end
+ def cache_namespace_name?
+ enabled?(ENV['CACHE_NAMESPACE_NAME'], default: true)
+ end
+
def knapsack?
!!(ENV['KNAPSACK_GENERATE_REPORT'] || ENV['KNAPSACK_REPORT_PATH'] || ENV['KNAPSACK_TEST_FILE_PATTERN'])
end
diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb
index 565bfd43f12..6b4cbe6af6e 100644
--- a/qa/qa/runtime/namespace.rb
+++ b/qa/qa/runtime/namespace.rb
@@ -9,14 +9,19 @@ module QA
@time ||= Time.now
end
- def name
+ def name(reset_cache: !Runtime::Env.cache_namespace_name?)
# If any changes are made to the name tag, following script has to be considered:
# https://ops.gitlab.net/gitlab-com/gl-infra/traffic-generator/blob/master/bin/janitor.bash
- @name ||= Runtime::Env.namespace_name || "qa-test-#{time.strftime('%Y-%m-%d-%H-%M-%S')}-#{SecureRandom.hex(8)}"
+ reset_name_cache if reset_cache
+ @name ||= Runtime::Env.namespace_name || "qa-test-#{time.strftime('%Y-%m-%d-%H-%M-%S')}-#{SecureRandom.hex(8)}" # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
+ def reset_name_cache
+ @name = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def path
- "#{sandbox_name}/#{name}"
+ "#{sandbox_name}/#{name(reset_cache: false)}"
end
def sandbox_name
diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb
index 115701c5c02..b011978dce6 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb
@@ -17,6 +17,7 @@ module QA
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'project-to-test-milestones'
+ project.group = group
end
end
diff --git a/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb
index af9c9a9e48f..59ec2774d26 100644
--- a/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb
+++ b/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb
@@ -65,7 +65,7 @@ module QA
end
end
- it 'uses templating variables for metrics dashboards' do
+ it 'uses templating variables for metrics dashboards', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/240984', type: :investigating } do
templating_dashboard_yml = Pathname
.new(__dir__)
.join('../../../../fixtures/metrics_dashboards/templating.yml')
diff --git a/qa/spec/runtime/namespace_spec.rb b/qa/spec/runtime/namespace_spec.rb
new file mode 100644
index 00000000000..d24fa509f30
--- /dev/null
+++ b/qa/spec/runtime/namespace_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+describe QA::Runtime::Namespace do
+ include Helpers::StubENV
+
+ describe '.name' do
+ context 'when CACHE_NAMESPACE_NAME is not defined' do
+ before do
+ stub_env('CACHE_NAMESPACE_NAME', nil)
+ end
+
+ it 'caches name by default' do
+ name = described_class.name
+ expect(described_class.name).to eq(name)
+ end
+
+ it 'does not cache name when reset_cache is true' do
+ name = described_class.name
+ expect(described_class.name(reset_cache: true)).not_to eq(name)
+ end
+ end
+
+ context 'when CACHE_NAMESPACE_NAME is defined' do
+ before do
+ stub_env('CACHE_NAMESPACE_NAME', 'true')
+ end
+
+ it 'caches name by default' do
+ name = described_class.name
+ expect(described_class.name).to eq(name)
+ end
+
+ it 'caches name when reset_cache is false' do
+ name = described_class.name
+ expect(described_class.name(reset_cache: false)).to eq(name)
+ end
+
+ it 'does not cache name when reset_cache is true' do
+ name = described_class.name
+ expect(described_class.name(reset_cache: true)).not_to eq(name)
+ end
+ end
+ end
+
+ describe '.path' do
+ it 'is always cached' do
+ path = described_class.path
+ expect(described_class.path).to eq(path)
+ end
+ end
+end
diff --git a/spec/controllers/groups/settings/integrations_controller_spec.rb b/spec/controllers/groups/settings/integrations_controller_spec.rb
index d079f3f077e..b0ea76e5523 100644
--- a/spec/controllers/groups/settings/integrations_controller_spec.rb
+++ b/spec/controllers/groups/settings/integrations_controller_spec.rb
@@ -3,7 +3,6 @@
require 'spec_helper'
RSpec.describe Groups::Settings::IntegrationsController do
- let_it_be(:project) { create(:project) }
let(:user) { create(:user) }
let(:group) { create(:group) }
@@ -82,7 +81,7 @@ RSpec.describe Groups::Settings::IntegrationsController do
end
describe '#update' do
- let(:integration) { create(:jira_service, project: project) }
+ let(:integration) { create(:jira_service, project: nil, group_id: group.id) }
before do
group.add_owner(user)
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index 2b222331b55..fa9ec7413f8 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe InvitesController do
let_it_be(:user) { create(:user) }
let(:member) { create(:project_member, :invited, invite_token: token, invite_email: user.email) }
let(:project_members) { member.source.users }
+ let(:md5_member_global_id) { Digest::MD5.hexdigest(member.to_global_id.to_s) }
before do
controller.instance_variable_set(:@member, member)
@@ -14,9 +15,13 @@ RSpec.describe InvitesController do
end
describe 'GET #show' do
+ let(:params) { { id: token } }
+
+ subject(:request) { get :show, params: params }
+
it 'accepts user if invite email matches signed in user' do
expect do
- get :show, params: { id: token }
+ request
end.to change { project_members.include?(user) }.from(false).to(true)
expect(response).to have_gitlab_http_status(:found)
@@ -27,11 +32,105 @@ RSpec.describe InvitesController do
member.invite_email = 'bogus@email.com'
expect do
- get :show, params: { id: token }
+ request
end.not_to change { project_members.include?(user) }
expect(response).to have_gitlab_http_status(:ok)
expect(flash[:notice]).to be_nil
end
+
+ context 'when new_user_invite is not set' do
+ it 'does not track the user as experiment group' do
+ expect(Gitlab::Tracking).not_to receive(:event)
+
+ request
+ end
+ end
+
+ context 'when new_user_invite is experiment' do
+ let(:params) { { id: token, new_user_invite: 'experiment' } }
+
+ it 'tracks the user as experiment group' do
+ expect(Gitlab::Tracking).to receive(:event).with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'opened',
+ property: 'experiment_group',
+ value: md5_member_global_id
+ )
+ expect(Gitlab::Tracking).to receive(:event).with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'accepted',
+ property: 'experiment_group',
+ value: md5_member_global_id
+ )
+
+ request
+ end
+ end
+
+ context 'when new_user_invite is control' do
+ let(:params) { { id: token, new_user_invite: 'control' } }
+
+ it 'tracks the user as control group' do
+ expect(Gitlab::Tracking).to receive(:event).with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'opened',
+ property: 'control_group',
+ value: md5_member_global_id
+ )
+ expect(Gitlab::Tracking).to receive(:event).with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'accepted',
+ property: 'control_group',
+ value: md5_member_global_id
+ )
+
+ request
+ end
+ end
+ end
+
+ describe 'POST #accept' do
+ let(:params) { { id: token } }
+
+ subject(:request) { post :accept, params: params }
+
+ context 'when new_user_invite is not set' do
+ it 'does not track an event' do
+ expect(Gitlab::Tracking).not_to receive(:event)
+
+ request
+ end
+ end
+
+ context 'when new_user_invite is experiment' do
+ let(:params) { { id: token, new_user_invite: 'experiment' } }
+
+ it 'tracks the user as experiment group' do
+ expect(Gitlab::Tracking).to receive(:event).with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'accepted',
+ property: 'experiment_group',
+ value: md5_member_global_id
+ )
+
+ request
+ end
+ end
+
+ context 'when new_user_invite is control' do
+ let(:params) { { id: token, new_user_invite: 'control' } }
+
+ it 'tracks the user as control group' do
+ expect(Gitlab::Tracking).to receive(:event).with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'accepted',
+ property: 'control_group',
+ value: md5_member_global_id
+ )
+
+ request
+ end
+ end
end
end
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 99fe2ef9c0a..5c62de4d08d 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -26,6 +26,12 @@ FactoryBot.define do
closed_at { Time.now }
end
+ trait :with_alert do
+ after(:create) do |issue|
+ create(:alert_management_alert, project: issue.project, issue: issue)
+ end
+ end
+
after(:build) do |issue, evaluator|
issue.state_id = Issue.available_states[evaluator.state]
end
diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb
index 24353f8fe3a..282d6b76023 100644
--- a/spec/graphql/types/issue_type_spec.rb
+++ b/spec/graphql/types/issue_type_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe GitlabSchema.types['Issue'] do
fields = %i[id iid title description state reference author assignees participants labels milestone due_date
confidential discussion_locked upvotes downvotes user_notes_count web_path web_url relative_position
subscribed time_estimate total_time_spent closed_at created_at updated_at task_completion_status
- designs design_collection]
+ designs design_collection alert_management_alert]
fields.each do |field_name|
expect(described_class).to have_graphql_field(field_name)
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 7bd1fae8f91..b9f95a9eb00 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -868,36 +868,116 @@ RSpec.describe Notify do
end
end
- def invite_to_project(project, inviter:)
+ def invite_to_project(project, inviter:, user: nil)
create(
:project_member,
:developer,
project: project,
invite_token: '1234',
invite_email: 'toto@example.com',
- user: nil,
+ user: user,
created_by: inviter
)
end
describe 'project invitation' do
let(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
- let(:project_member) { invite_to_project(project, inviter: maintainer) }
+ let(:project_member) { invite_to_project(project, inviter: inviter) }
+ let(:inviter) { maintainer }
subject { described_class.member_invited_email('project', project_member.id, project_member.invite_token) }
- 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 'appearance header and footer enabled'
- it_behaves_like 'appearance header and footer not enabled'
+ context 'when invite_email_experiment is disabled' do
+ before do
+ stub_feature_flags(invite_email_experiment: false)
+ end
- it 'contains all the useful information' do
- is_expected.to have_subject "Invitation to join the #{project.full_name} project"
- is_expected.to have_body_text project.full_name
- is_expected.to have_body_text project.full_name
- is_expected.to have_body_text project_member.human_access
- is_expected.to have_body_text project_member.invite_token
+ 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 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
+ it 'contains all the useful information' do
+ is_expected.to have_subject "Invitation to join the #{project.full_name} project"
+ is_expected.to have_body_text project.full_name
+ is_expected.to have_body_text project_member.human_access
+ is_expected.to have_body_text project_member.invite_token
+ end
+
+ context 'when member is invited via an email address' do
+ it 'does add a param to the invite link' do
+ is_expected.to have_body_text 'new_user_invite=control'
+ end
+
+ it 'tracks an event' do
+ expect(Gitlab::Tracking).to receive(:event).with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'sent',
+ property: 'control_group'
+ )
+
+ subject.deliver_now
+ end
+ end
+
+ context 'when member is already a user' do
+ let(:project_member) { invite_to_project(project, inviter: maintainer, user: create(:user)) }
+
+ it 'does not add a param to the invite link' do
+ is_expected.not_to have_body_text 'new_user_invite'
+ end
+
+ it 'does not track an event' do
+ expect(Gitlab::Tracking).not_to receive(:event)
+
+ subject.deliver_now
+ end
+ end
+ end
+
+ context 'when invite_email_experiment is enabled' do
+ before do
+ stub_feature_flags(invite_email_experiment: true)
+ 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"
+
+ context 'when there is no inviter' do
+ let(:inviter) { nil }
+
+ it 'contains all the useful information' do
+ is_expected.to have_subject "Invitation to join the #{project.full_name} project"
+ is_expected.to have_body_text project.full_name
+ is_expected.to have_body_text project_member.human_access.downcase
+ is_expected.to have_body_text project_member.invite_token
+ end
+ end
+
+ context 'when there is an inviter' do
+ it 'contains all the useful information' do
+ is_expected.to have_subject "#{inviter.name} invited you to join GitLab"
+ is_expected.to have_body_text project.full_name
+ is_expected.to have_body_text project_member.human_access.downcase
+ is_expected.to have_body_text project_member.invite_token
+ end
+ end
+
+ it 'adds a param to the invite link' do
+ is_expected.to have_body_text 'new_user_invite=experiment'
+ end
+
+ it 'tracks an event' do
+ expect(Gitlab::Tracking).to receive(:event).with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'sent',
+ property: 'experiment_group'
+ )
+
+ subject.deliver_now
+ end
end
end
@@ -1416,37 +1496,115 @@ RSpec.describe Notify do
end
end
- def invite_to_group(group, inviter:)
+ def invite_to_group(group, inviter:, user: nil)
create(
:group_member,
:developer,
group: group,
invite_token: '1234',
invite_email: 'toto@example.com',
- user: nil,
+ user: user,
created_by: inviter
)
end
describe 'group invitation' do
let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } }
- let(:group_member) { invite_to_group(group, inviter: owner) }
+ let(:group_member) { invite_to_group(group, inviter: inviter) }
+ let(:inviter) { owner }
subject { described_class.member_invited_email('group', group_member.id, group_member.invite_token) }
- 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 'appearance header and footer enabled'
- it_behaves_like 'appearance header and footer not enabled'
- it_behaves_like 'it requires a group'
+ context 'when invite_email_experiment is disabled' do
+ before do
+ stub_feature_flags(invite_email_experiment: false)
+ end
- it 'contains all the useful information' do
- is_expected.to have_subject "Invitation to join the #{group.name} group"
- is_expected.to have_body_text group.name
- is_expected.to have_body_text group.web_url
- is_expected.to have_body_text group_member.human_access
- is_expected.to have_body_text group_member.invite_token
+ 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 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+ it_behaves_like 'it requires a group'
+
+ it 'contains all the useful information' do
+ is_expected.to have_subject "Invitation to join the #{group.name} group"
+ is_expected.to have_body_text group.name
+ is_expected.to have_body_text group.web_url
+ is_expected.to have_body_text group_member.human_access
+ is_expected.to have_body_text group_member.invite_token
+ end
+
+ context 'when member is invited via an email address' do
+ it 'does add a param to the invite link' do
+ is_expected.to have_body_text 'new_user_invite=control'
+ end
+
+ it 'tracks an event' do
+ expect(Gitlab::Tracking).to receive(:event).with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'sent',
+ property: 'control_group'
+ )
+
+ subject.deliver_now
+ end
+ end
+
+ context 'when member is already a user' do
+ let(:group_member) { invite_to_group(group, inviter: owner, user: create(:user)) }
+
+ it 'does not add a param to the invite link' do
+ is_expected.not_to have_body_text 'new_user_invite'
+ end
+
+ it 'does not track an event' do
+ expect(Gitlab::Tracking).not_to receive(:event)
+
+ subject.deliver_now
+ end
+ end
+ end
+
+ context 'when invite_email_experiment is enabled' do
+ 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 'it requires a group'
+
+ context 'when there is no inviter' do
+ let(:inviter) { nil }
+
+ it 'contains all the useful information' do
+ is_expected.to have_subject "Invitation to join the #{group.name} group"
+ is_expected.to have_body_text group.name
+ is_expected.to have_body_text group_member.human_access.downcase
+ is_expected.to have_body_text group_member.invite_token
+ end
+ end
+
+ context 'when there is an inviter' do
+ it 'contains all the useful information' do
+ is_expected.to have_subject "#{group_member.created_by.name} invited you to join GitLab"
+ is_expected.to have_body_text group.name
+ is_expected.to have_body_text group_member.human_access.downcase
+ is_expected.to have_body_text group_member.invite_token
+ end
+ end
+
+ it 'does add a param to the invite link' do
+ is_expected.to have_body_text 'new_user_invite'
+ end
+
+ it 'tracks an event' do
+ expect(Gitlab::Tracking).to receive(:event).with(
+ 'Growth::Acquisition::Experiment::InviteEmail',
+ 'sent',
+ property: 'experiment_group'
+ )
+
+ subject.deliver_now
+ end
end
end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index a3ed39abfb3..8da164e041c 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -617,6 +617,24 @@ RSpec.describe Member do
end
end
+ describe "#invite_to_unknown_user?" do
+ subject { member.invite_to_unknown_user? }
+
+ let(:member) { create(:project_member, invite_email: "user@example.com", invite_token: '1234', user: user) }
+
+ context 'when user is nil' do
+ let(:user) { nil }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when user is set' do
+ let(:user) { build(:user) }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
describe "destroying a record", :delete do
it "refreshes user's authorized projects" do
project = create(:project, :private)
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index d001b6ec0f1..6dddcb0eca4 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Service do
+ let_it_be(:group) { create(:group) }
+
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -13,7 +15,6 @@ RSpec.describe Service do
describe 'validations' do
using RSpec::Parameterized::TableSyntax
- let(:group) { create(:group) }
let(:project) { create(:project) }
it { is_expected.to validate_presence_of(:type) }
@@ -91,17 +92,12 @@ RSpec.describe Service do
end
end
- describe '#operating?' do
- it 'is false when the service is not active' do
- expect(build(:service).operating?).to eq(false)
- end
-
- it 'is false when the service is not persisted' do
- expect(build(:service, active: true).operating?).to eq(false)
- end
+ describe '.by_group' do
+ let!(:service1) { create(:jira_service, project_id: nil, group_id: group.id) }
+ let!(:service2) { create(:jira_service) }
- it 'is true when the service is active and persisted' do
- expect(create(:service, active: true).operating?).to eq(true)
+ it 'returns the right group service' do
+ expect(described_class.by_group(group)).to match_array([service1])
end
end
@@ -134,6 +130,20 @@ RSpec.describe Service do
end
end
+ describe '#operating?' do
+ it 'is false when the service is not active' do
+ expect(build(:service).operating?).to eq(false)
+ end
+
+ it 'is false when the service is not persisted' do
+ expect(build(:service, active: true).operating?).to eq(false)
+ end
+
+ it 'is true when the service is active and persisted' do
+ expect(create(:service, active: true).operating?).to eq(true)
+ end
+ end
+
describe "Test Button" do
describe '#can_test?' do
subject { service.can_test? }
@@ -189,14 +199,27 @@ RSpec.describe Service do
end
end
- describe '.find_or_initialize_instances' do
+ describe '.find_or_initialize_integration' do
+ let!(:service1) { create(:jira_service, project_id: nil, group_id: group.id) }
+ let!(:service2) { create(:jira_service) }
+
+ it 'returns the right service' do
+ expect(Service.find_or_initialize_integration('jira', group_id: group)).to eq(service1)
+ end
+
+ it 'does not create a new service' do
+ expect { Service.find_or_initialize_integration('redmine', group_id: group) }.not_to change { Service.count }
+ end
+ end
+
+ describe '.find_or_initialize_all' do
shared_examples 'service instances' do
it 'returns the available service instances' do
- expect(Service.find_or_initialize_instances.pluck(:type)).to match_array(Service.available_services_types)
+ expect(Service.find_or_initialize_all(Service.instances).pluck(:type)).to match_array(Service.available_services_types)
end
it 'does not create service instances' do
- expect { Service.find_or_initialize_instances }.not_to change { Service.count }
+ expect { Service.find_or_initialize_all(Service.instances) }.not_to change { Service.count }
end
end
@@ -211,9 +234,9 @@ RSpec.describe Service do
it_behaves_like 'service instances'
- context 'with a previous existing service (Previous) and a new service (Asana)' do
+ context 'with a previous existing service (MockCiService) and a new service (Asana)' do
before do
- Service.insert(type: 'PreviousService', instance: true)
+ Service.insert(type: 'MockCiService', instance: true)
Service.delete_by(type: 'AsanaService', instance: true)
end
diff --git a/spec/policies/metrics/dashboard/annotation_policy_spec.rb b/spec/policies/metrics/dashboard/annotation_policy_spec.rb
index 0c59b39ae3e..9ea9f843f2c 100644
--- a/spec/policies/metrics/dashboard/annotation_policy_spec.rb
+++ b/spec/policies/metrics/dashboard/annotation_policy_spec.rb
@@ -3,6 +3,10 @@
require 'spec_helper'
RSpec.describe Metrics::Dashboard::AnnotationPolicy, :models do
+ let(:policy) { described_class.new(user, annotation) }
+
+ let_it_be(:user) { create(:user) }
+
shared_examples 'metrics dashboard annotation policy' do
context 'when guest' do
before do
@@ -51,23 +55,21 @@ RSpec.describe Metrics::Dashboard::AnnotationPolicy, :models do
describe 'rules' do
context 'environments annotation' do
- let(:annotation) { create(:metrics_dashboard_annotation, environment: environment) }
- let(:environment) { create(:environment) }
- let!(:project) { environment.project }
- let(:user) { create(:user) }
- let(:policy) { described_class.new(user, annotation) }
+ let_it_be(:environment) { create(:environment) }
+ let_it_be(:annotation) { create(:metrics_dashboard_annotation, environment: environment) }
- it_behaves_like 'metrics dashboard annotation policy'
+ it_behaves_like 'metrics dashboard annotation policy' do
+ let(:project) { environment.project }
+ end
end
context 'cluster annotation' do
- let(:annotation) { create(:metrics_dashboard_annotation, environment: nil, cluster: cluster) }
- let(:cluster) { create(:cluster, :project) }
- let(:project) { cluster.project }
- let(:user) { create(:user) }
- let(:policy) { described_class.new(user, annotation) }
+ let_it_be(:cluster) { create(:cluster, :project) }
+ let_it_be(:annotation) { create(:metrics_dashboard_annotation, environment: nil, cluster: cluster) }
- it_behaves_like 'metrics dashboard annotation policy'
+ it_behaves_like 'metrics dashboard annotation policy' do
+ let(:project) { cluster.project }
+ end
end
end
end
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 06e613a09bc..55e34a484f9 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'getting an issue list for a project' do
let(:issues_data) { graphql_data['project']['issues']['edges'] }
let!(:issues) do
[create(:issue, project: project, discussion_locked: true),
- create(:issue, project: project)]
+ create(:issue, :with_alert, project: project)]
end
let(:fields) do
@@ -256,6 +256,40 @@ RSpec.describe 'getting an issue list for a project' do
end
end
+ context 'fetching alert management alert' do
+ let(:fields) do
+ <<~QUERY
+ edges {
+ node {
+ id
+ alertManagementAlert {
+ title
+ }
+ }
+ }
+ QUERY
+ end
+
+ # Alerts need to reporter and above
+ before do
+ project.add_reporter(current_user)
+ end
+
+ it 'avoids N+1 queries' do
+ control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) }
+
+ create(:alert_management_alert, :with_issue, project: project )
+
+ expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(control)
+ end
+
+ it 'returns the alert data' do
+ post_graphql(query, current_user: current_user)
+
+ issues_data
+ end
+ end
+
def grab_iids(data = issues_data)
data.map do |issue|
issue.dig('node', 'iid').to_i