Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a9fa13e4ba
commit
bf1a77ead7
80 changed files with 488 additions and 354 deletions
|
@ -1 +1 @@
|
||||||
838d4c8bc4d82ef2ad79437b37f0d8b43394c7f7
|
d56698332e3e5380c4e6723828bb01ac32edd4c9
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -393,6 +393,8 @@ group :development, :test do
|
||||||
gem 'parallel', '~> 1.19', require: false
|
gem 'parallel', '~> 1.19', require: false
|
||||||
|
|
||||||
gem 'test_file_finder', '~> 0.1.3'
|
gem 'test_file_finder', '~> 0.1.3'
|
||||||
|
|
||||||
|
gem 'sigdump', '~> 0.2.4', require: 'sigdump/setup'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development, :test, :danger do
|
group :development, :test, :danger do
|
||||||
|
|
|
@ -1181,6 +1181,7 @@ GEM
|
||||||
sidekiq-cron (1.2.0)
|
sidekiq-cron (1.2.0)
|
||||||
fugit (~> 1.1)
|
fugit (~> 1.1)
|
||||||
sidekiq (>= 4.2.1)
|
sidekiq (>= 4.2.1)
|
||||||
|
sigdump (0.2.4)
|
||||||
signet (0.14.0)
|
signet (0.14.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
|
@ -1622,6 +1623,7 @@ DEPENDENCIES
|
||||||
shoulda-matchers (~> 4.0.1)
|
shoulda-matchers (~> 4.0.1)
|
||||||
sidekiq (~> 6.4)
|
sidekiq (~> 6.4)
|
||||||
sidekiq-cron (~> 1.2)
|
sidekiq-cron (~> 1.2)
|
||||||
|
sigdump (~> 0.2.4)
|
||||||
simple_po_parser (~> 1.1.2)
|
simple_po_parser (~> 1.1.2)
|
||||||
simplecov (~> 0.18.5)
|
simplecov (~> 0.18.5)
|
||||||
simplecov-cobertura (~> 1.3.1)
|
simplecov-cobertura (~> 1.3.1)
|
||||||
|
|
|
@ -377,10 +377,6 @@ span.idiff {
|
||||||
color: $gl-text-color;
|
color: $gl-text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-actions .ide-edit-button {
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
@include media-breakpoint-down(md) {
|
||||||
.file-actions {
|
.file-actions {
|
||||||
margin-top: $gl-padding-8;
|
margin-top: $gl-padding-8;
|
||||||
|
|
|
@ -40,7 +40,7 @@ module Projects
|
||||||
avenues = [authorizable_project_members]
|
avenues = [authorizable_project_members]
|
||||||
|
|
||||||
avenues << if project.personal?
|
avenues << if project.personal?
|
||||||
project_owner_acting_as_maintainer
|
project_owner
|
||||||
else
|
else
|
||||||
authorizable_group_members
|
authorizable_group_members
|
||||||
end
|
end
|
||||||
|
@ -85,9 +85,15 @@ module Projects
|
||||||
Member.from_union(members)
|
Member.from_union(members)
|
||||||
end
|
end
|
||||||
|
|
||||||
def project_owner_acting_as_maintainer
|
# workaround until we migrate Project#owners to have membership with
|
||||||
|
# OWNER access level
|
||||||
|
def project_owner
|
||||||
user_id = project.namespace.owner.id
|
user_id = project.namespace.owner.id
|
||||||
access_level = Gitlab::Access::MAINTAINER
|
access_level = if ::Feature.enabled?(:personal_project_owner_with_owner_access, default_enabled: :yaml)
|
||||||
|
Gitlab::Access::OWNER
|
||||||
|
else
|
||||||
|
Gitlab::Access::MAINTAINER
|
||||||
|
end
|
||||||
|
|
||||||
Member
|
Member
|
||||||
.from(generate_from_statement([[user_id, access_level]])) # rubocop: disable CodeReuse/ActiveRecord
|
.from(generate_from_statement([[user_id, access_level]])) # rubocop: disable CodeReuse/ActiveRecord
|
||||||
|
|
|
@ -65,40 +65,13 @@ module BlobHelper
|
||||||
return unless blob = readable_blob(options, path, project, ref)
|
return unless blob = readable_blob(options, path, project, ref)
|
||||||
|
|
||||||
common_classes = "btn gl-button btn-confirm js-edit-blob gl-ml-3 #{options[:extra_class]}"
|
common_classes = "btn gl-button btn-confirm js-edit-blob gl-ml-3 #{options[:extra_class]}"
|
||||||
data = { track_action: 'click_edit', track_label: 'edit' }
|
|
||||||
|
|
||||||
if Feature.enabled?(:web_ide_primary_edit, project.group)
|
|
||||||
common_classes += " btn-inverted"
|
|
||||||
data[:track_property] = 'secondary'
|
|
||||||
end
|
|
||||||
|
|
||||||
edit_button_tag(blob,
|
edit_button_tag(blob,
|
||||||
common_classes,
|
common_classes,
|
||||||
_('Edit'),
|
_('Edit'),
|
||||||
edit_blob_path(project, ref, path, options),
|
edit_blob_path(project, ref, path, options),
|
||||||
project,
|
project,
|
||||||
ref,
|
ref)
|
||||||
data)
|
|
||||||
end
|
|
||||||
|
|
||||||
def ide_edit_button(project = @project, ref = @ref, path = @path, blob:)
|
|
||||||
return unless blob
|
|
||||||
|
|
||||||
common_classes = 'btn gl-button btn-confirm ide-edit-button gl-ml-3'
|
|
||||||
data = { track_action: 'click_edit_ide', track_label: 'web_ide' }
|
|
||||||
|
|
||||||
unless Feature.enabled?(:web_ide_primary_edit, project.group)
|
|
||||||
common_classes += " btn-inverted"
|
|
||||||
data[:track_property] = 'secondary'
|
|
||||||
end
|
|
||||||
|
|
||||||
edit_button_tag(blob,
|
|
||||||
common_classes,
|
|
||||||
_('Web IDE'),
|
|
||||||
ide_edit_path(project, ref, path),
|
|
||||||
project,
|
|
||||||
ref,
|
|
||||||
data)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def modify_file_button(project = @project, ref = @ref, path = @path, blob:, label:, action:, btn_class:, modal_type:)
|
def modify_file_button(project = @project, ref = @ref, path = @path, blob:, label:, action:, btn_class:, modal_type:)
|
||||||
|
@ -363,16 +336,16 @@ module BlobHelper
|
||||||
content_tag(:span, button, class: 'has-tooltip', title: _('You can only edit files when you are on a branch'), data: { container: 'body' })
|
content_tag(:span, button, class: 'has-tooltip', title: _('You can only edit files when you are on a branch'), data: { container: 'body' })
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit_link_tag(link_text, edit_path, common_classes, data)
|
def edit_link_tag(link_text, edit_path, common_classes)
|
||||||
link_to link_text, edit_path, class: "#{common_classes}", data: data
|
link_to link_text, edit_path, class: "#{common_classes}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit_button_tag(blob, common_classes, text, edit_path, project, ref, data)
|
def edit_button_tag(blob, common_classes, text, edit_path, project, ref)
|
||||||
if !on_top_of_branch?(project, ref)
|
if !on_top_of_branch?(project, ref)
|
||||||
edit_disabled_button_tag(text, common_classes)
|
edit_disabled_button_tag(text, common_classes)
|
||||||
# This condition only applies to users who are logged in
|
# This condition only applies to users who are logged in
|
||||||
elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
|
elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
|
||||||
edit_link_tag(text, edit_path, common_classes, data)
|
edit_link_tag(text, edit_path, common_classes)
|
||||||
elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
|
elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
|
||||||
edit_fork_button_tag(common_classes, project, text, edit_blob_fork_params(edit_path))
|
edit_fork_button_tag(common_classes, project, text, edit_blob_fork_params(edit_path))
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,6 +14,14 @@ module MergeRequestReviewerState
|
||||||
presence: true,
|
presence: true,
|
||||||
inclusion: { in: self.states.keys }
|
inclusion: { in: self.states.keys }
|
||||||
|
|
||||||
|
belongs_to :updated_state_by, class_name: 'User', foreign_key: :updated_state_by_user_id
|
||||||
|
|
||||||
after_initialize :set_state, unless: :persisted?
|
after_initialize :set_state, unless: :persisted?
|
||||||
|
|
||||||
|
def attention_requested_by
|
||||||
|
return unless attention_requested?
|
||||||
|
|
||||||
|
updated_state_by
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,8 +8,14 @@ module SelectForProjectAuthorization
|
||||||
select("projects.id AS project_id", "members.access_level")
|
select("projects.id AS project_id", "members.access_level")
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_as_maintainer_for_project_authorization
|
# workaround until we migrate Project#owners to have membership with
|
||||||
select(["projects.id AS project_id", "#{Gitlab::Access::MAINTAINER} AS access_level"])
|
# OWNER access level
|
||||||
|
def select_project_owner_for_project_authorization
|
||||||
|
if ::Feature.enabled?(:personal_project_owner_with_owner_access, default_enabled: :yaml)
|
||||||
|
select(["projects.id AS project_id", "#{Gitlab::Access::OWNER} AS access_level"])
|
||||||
|
else
|
||||||
|
select(["projects.id AS project_id", "#{Gitlab::Access::MAINTAINER} AS access_level"])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -94,9 +94,16 @@ class ProjectMember < Member
|
||||||
|
|
||||||
override :access_level_inclusion
|
override :access_level_inclusion
|
||||||
def access_level_inclusion
|
def access_level_inclusion
|
||||||
return if access_level.in?(Gitlab::Access.values)
|
allowed_values = if ::Feature.enabled?(:personal_project_owner_with_owner_access,
|
||||||
|
default_enabled: :yaml)
|
||||||
|
Gitlab::Access.all_values
|
||||||
|
else
|
||||||
|
Gitlab::Access.values
|
||||||
|
end
|
||||||
|
|
||||||
errors.add(:access_level, "is not included in the list")
|
unless access_level.in?(allowed_values)
|
||||||
|
errors.add(:access_level, "is not included in the list")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
override :refresh_member_authorized_projects
|
override :refresh_member_authorized_projects
|
||||||
|
|
|
@ -1936,10 +1936,18 @@ class MergeRequest < ApplicationRecord
|
||||||
merge_request_assignees.find_by(user_id: user.id)
|
merge_request_assignees.find_by(user_id: user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def merge_request_assignees_with(user_ids)
|
||||||
|
merge_request_assignees.where(user_id: user_ids)
|
||||||
|
end
|
||||||
|
|
||||||
def find_reviewer(user)
|
def find_reviewer(user)
|
||||||
merge_request_reviewers.find_by(user_id: user.id)
|
merge_request_reviewers.find_by(user_id: user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def merge_request_reviewers_with(user_ids)
|
||||||
|
merge_request_reviewers.where(user_id: user_ids)
|
||||||
|
end
|
||||||
|
|
||||||
def enabled_reports
|
def enabled_reports
|
||||||
{
|
{
|
||||||
sast: report_type_enabled?(:sast),
|
sast: report_type_enabled?(:sast),
|
||||||
|
|
|
@ -459,7 +459,7 @@ class Project < ApplicationRecord
|
||||||
delegate :name, to: :owner, allow_nil: true, prefix: true
|
delegate :name, to: :owner, allow_nil: true, prefix: true
|
||||||
delegate :members, to: :team, prefix: true
|
delegate :members, to: :team, prefix: true
|
||||||
delegate :add_user, :add_users, to: :team
|
delegate :add_user, :add_users, to: :team
|
||||||
delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_role, to: :team
|
delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_owner, :add_role, to: :team
|
||||||
delegate :group_runners_enabled, :group_runners_enabled=, to: :ci_cd_settings, allow_nil: true
|
delegate :group_runners_enabled, :group_runners_enabled=, to: :ci_cd_settings, allow_nil: true
|
||||||
delegate :root_ancestor, to: :namespace, allow_nil: true
|
delegate :root_ancestor, to: :namespace, allow_nil: true
|
||||||
delegate :last_pipeline, to: :commit, allow_nil: true
|
delegate :last_pipeline, to: :commit, allow_nil: true
|
||||||
|
|
|
@ -23,6 +23,10 @@ class ProjectTeam
|
||||||
add_user(user, :maintainer, current_user: current_user)
|
add_user(user, :maintainer, current_user: current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_owner(user, current_user: nil)
|
||||||
|
add_user(user, :owner, current_user: current_user)
|
||||||
|
end
|
||||||
|
|
||||||
def add_role(user, role, current_user: nil)
|
def add_role(user, role, current_user: nil)
|
||||||
public_send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend
|
public_send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend
|
||||||
end
|
end
|
||||||
|
@ -103,7 +107,9 @@ class ProjectTeam
|
||||||
if group
|
if group
|
||||||
group.owners
|
group.owners
|
||||||
else
|
else
|
||||||
[project.owner]
|
# workaround until we migrate Project#owners to have membership with
|
||||||
|
# OWNER access level
|
||||||
|
Array.wrap(fetch_members(Gitlab::Access::OWNER)) | Array.wrap(project.owner)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ module Members
|
||||||
module Projects
|
module Projects
|
||||||
class CreatorService < Members::CreatorService
|
class CreatorService < Members::CreatorService
|
||||||
def self.access_levels
|
def self.access_levels
|
||||||
Gitlab::Access.sym_options
|
Gitlab::Access.sym_options_with_owner
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -61,6 +61,8 @@ module MergeRequests
|
||||||
|
|
||||||
unless new_reviewers.include?(current_user)
|
unless new_reviewers.include?(current_user)
|
||||||
remove_attention_requested(merge_request, current_user)
|
remove_attention_requested(merge_request, current_user)
|
||||||
|
|
||||||
|
merge_request.merge_request_reviewers_with(new_reviewers).update_all(updated_state_by_user_id: current_user.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ module MergeRequests
|
||||||
merge_request_activity_counter.track_users_assigned_to_mr(users: new_assignees)
|
merge_request_activity_counter.track_users_assigned_to_mr(users: new_assignees)
|
||||||
merge_request_activity_counter.track_assignees_changed_action(user: current_user)
|
merge_request_activity_counter.track_assignees_changed_action(user: current_user)
|
||||||
|
|
||||||
|
merge_request.merge_request_assignees_with(new_assignees).update_all(updated_state_by_user_id: current_user.id)
|
||||||
|
|
||||||
execute_assignees_hooks(merge_request, old_assignees) if options[:execute_hooks]
|
execute_assignees_hooks(merge_request, old_assignees) if options[:execute_hooks]
|
||||||
|
|
||||||
unless new_assignees.include?(current_user)
|
unless new_assignees.include?(current_user)
|
||||||
|
|
|
@ -59,7 +59,8 @@ module MergeRequests
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_state(reviewer_or_assignee)
|
def update_state(reviewer_or_assignee)
|
||||||
reviewer_or_assignee&.update(state: reviewer_or_assignee&.attention_requested? ? :reviewed : :attention_requested)
|
reviewer_or_assignee&.update(state: reviewer_or_assignee&.attention_requested? ? :reviewed : :attention_requested,
|
||||||
|
updated_state_by: current_user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,6 +14,7 @@ module NotificationRecipients
|
||||||
return [] unless project
|
return [] unless project
|
||||||
|
|
||||||
add_recipients(project.team.maintainers, :mention, nil)
|
add_recipients(project.team.maintainers, :mention, nil)
|
||||||
|
add_recipients(project.team.owners, :mention, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def acting_user
|
def acting_user
|
||||||
|
|
|
@ -147,7 +147,11 @@ module Projects
|
||||||
priority: UserProjectAccessChangedService::LOW_PRIORITY
|
priority: UserProjectAccessChangedService::LOW_PRIORITY
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
@project.add_maintainer(@project.namespace.owner, current_user: current_user)
|
if ::Feature.enabled?(:personal_project_owner_with_owner_access, default_enabled: :yaml)
|
||||||
|
@project.add_owner(@project.namespace.owner, current_user: current_user)
|
||||||
|
else
|
||||||
|
@project.add_maintainer(@project.namespace.owner, current_user: current_user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
title: s_('AdminArea|Get security updates from GitLab and stay up to date'),
|
title: s_('AdminArea|Get security updates from GitLab and stay up to date'),
|
||||||
variant: :tip,
|
variant: :tip,
|
||||||
alert_class: 'js-security-newsletter-callout',
|
alert_class: 'js-security-newsletter-callout',
|
||||||
is_contained: true,
|
|
||||||
alert_data: { feature_id: Users::CalloutsHelper::SECURITY_NEWSLETTER_CALLOUT, dismiss_endpoint: callouts_path, defer_links: 'true' },
|
alert_data: { feature_id: Users::CalloutsHelper::SECURITY_NEWSLETTER_CALLOUT, dismiss_endpoint: callouts_path, defer_links: 'true' },
|
||||||
close_button_data: { testid: 'close-security-newsletter-callout' } do
|
close_button_data: { testid: 'close-security-newsletter-callout' } do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
variant: :warning,
|
variant: :warning,
|
||||||
alert_class: 'hidden js-cluster-api-unreachable',
|
alert_class: 'hidden js-cluster-api-unreachable',
|
||||||
is_contained: true,
|
|
||||||
close_button_class: 'js-close' do
|
close_button_class: 'js-close' do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
= s_('ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct.')
|
= s_('ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct.')
|
||||||
|
@ -17,7 +16,6 @@
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
variant: :warning,
|
variant: :warning,
|
||||||
alert_class: 'hidden js-cluster-authentication-failure js-cluster-api-unreachable',
|
alert_class: 'hidden js-cluster-authentication-failure js-cluster-api-unreachable',
|
||||||
is_contained: true,
|
|
||||||
close_button_class: 'js-close' do
|
close_button_class: 'js-close' do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
= s_('ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid.')
|
= s_('ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid.')
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
variant: :info,
|
variant: :info,
|
||||||
alert_class: 'gl-my-5',
|
alert_class: 'gl-my-5',
|
||||||
is_contained: true,
|
|
||||||
dismissible: false do
|
dismissible: false do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
= s_('Profiles|Some options are unavailable for LDAP accounts')
|
= s_('Profiles|Some options are unavailable for LDAP accounts')
|
||||||
|
@ -14,7 +13,6 @@
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
variant: :success,
|
variant: :success,
|
||||||
alert_class: 'gl-my-5',
|
alert_class: 'gl-my-5',
|
||||||
is_contained: true,
|
|
||||||
close_button_class: 'js-close-2fa-enabled-success-alert' do
|
close_button_class: 'js-close-2fa-enabled-success-alert' do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
= html_escape(_('You have set up 2FA for your account! If you lose access to your 2FA device, you can use your recovery codes to access your account. Alternatively, if you upload an SSH key, you can %{anchorOpen}use that key to generate additional recovery codes%{anchorClose}.')) % { anchorOpen: '<a href="%{href}">'.html_safe % { href: help_page_path('user/profile/account/two_factor_authentication', anchor: 'generate-new-recovery-codes-using-ssh') }, anchorClose: '</a>'.html_safe }
|
= html_escape(_('You have set up 2FA for your account! If you lose access to your 2FA device, you can use your recovery codes to access your account. Alternatively, if you upload an SSH key, you can %{anchorOpen}use that key to generate additional recovery codes%{anchorClose}.')) % { anchorOpen: '<a href="%{href}">'.html_safe % { href: help_page_path('user/profile/account/two_factor_authentication', anchor: 'generate-new-recovery-codes-using-ssh') }, anchorClose: '</a>'.html_safe }
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
- if @error
|
- if @error
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
variant: :danger,
|
variant: :danger,
|
||||||
close_button_class: 'js-close',
|
close_button_class: 'js-close' do
|
||||||
is_contained: true do
|
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
= @error
|
= @error
|
||||||
%h3.page-title
|
%h3.page-title
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
title: _('Too many changes to show.'),
|
title: _('Too many changes to show.'),
|
||||||
variant: :warning,
|
variant: :warning,
|
||||||
is_contained: true,
|
|
||||||
alert_class: 'gl-mb-5' do
|
alert_class: 'gl-mb-5' do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
= html_escape(_("To preserve performance only %{strong_open}%{display_size} of %{real_size}%{strong_close} files are displayed.")) % { display_size: diff_files.size, real_size: diff_files.real_size, strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe }
|
= html_escape(_("To preserve performance only %{strong_open}%{display_size} of %{real_size}%{strong_close} files are displayed.")) % { display_size: diff_files.size, real_size: diff_files.real_size, strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe }
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
title: _('Fork Error!'),
|
title: _('Fork Error!'),
|
||||||
variant: :danger,
|
variant: :danger,
|
||||||
alert_class: 'gl-mt-5',
|
alert_class: 'gl-mt-5',
|
||||||
is_contained: true,
|
|
||||||
dismissible: false do
|
dismissible: false do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
%p
|
%p
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
variant: :warning,
|
variant: :warning,
|
||||||
is_contained: true,
|
|
||||||
close_button_class: 'js-close',
|
close_button_class: 'js-close',
|
||||||
alert_class: 'hide js-alert-moved-from-service-desk-warning gl-mt-5' do
|
alert_class: 'hide js-alert-moved-from-service-desk-warning gl-mt-5' do
|
||||||
.gl-alert-body.gl-mr-3
|
.gl-alert-body.gl-mr-3
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
variant: :info,
|
variant: :info,
|
||||||
dismissible: false,
|
dismissible: false,
|
||||||
is_contained: true,
|
|
||||||
alert_data: { testid: 'no-issues-alert' },
|
alert_data: { testid: 'no-issues-alert' },
|
||||||
alert_class: 'gl-mt-3 gl-mb-5' do
|
alert_class: 'gl-mt-3 gl-mb-5' do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
|
|
|
@ -8,16 +8,14 @@
|
||||||
- close_button_class = local_assigns.fetch(:close_button_class, nil)
|
- close_button_class = local_assigns.fetch(:close_button_class, nil)
|
||||||
- close_button_data = local_assigns.fetch(:close_button_data, nil)
|
- close_button_data = local_assigns.fetch(:close_button_data, nil)
|
||||||
- icon = icons[variant]
|
- icon = icons[variant]
|
||||||
- alert_container_class = [container_class, @content_class] unless fluid_layout || local_assigns.fetch(:is_contained, false)
|
|
||||||
|
|
||||||
%div{ role: 'alert', class: ['gl-alert', "gl-alert-#{variant}", alert_class], data: alert_data }
|
%div{ role: 'alert', class: ['gl-alert', "gl-alert-#{variant}", alert_class], data: alert_data }
|
||||||
.gl-alert-container{ class: alert_container_class }
|
= sprite_icon(icon, css_class: "gl-alert-icon#{' gl-alert-icon-no-title' if title.nil?}")
|
||||||
= sprite_icon(icon, size: 16, css_class: "gl-alert-icon#{' gl-alert-icon-no-title' if title.nil?}")
|
- if dismissible
|
||||||
- if dismissible
|
%button.btn.gl-dismiss-btn.btn-default.btn-sm.gl-button.btn-default-tertiary.btn-icon.js-close{ type: 'button', aria: { label: _('Dismiss') }, class: close_button_class, data: close_button_data }
|
||||||
%button.btn.gl-dismiss-btn.btn-default.btn-sm.gl-button.btn-default-tertiary.btn-icon.js-close{ type: 'button', aria: { label: _('Dismiss') }, class: close_button_class, data: close_button_data }
|
= sprite_icon('close')
|
||||||
= sprite_icon('close', size: 16)
|
.gl-alert-content{ role: 'alert' }
|
||||||
.gl-alert-content{ role: 'alert' }
|
- if title
|
||||||
- if title
|
%h4.gl-alert-title
|
||||||
%h4.gl-alert-title
|
= title
|
||||||
= title
|
= yield
|
||||||
= yield
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
- if session[:ask_for_usage_stats_consent]
|
- if session[:ask_for_usage_stats_consent]
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
variant: :info,
|
variant: :info,
|
||||||
is_contained: true,
|
|
||||||
alert_class: 'service-ping-consent-message' do
|
alert_class: 'service-ping-consent-message' do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
- docs_link = link_to _('collect usage information'), help_page_path('user/admin_area/settings/usage_statistics.md'), class: 'gl-link'
|
- docs_link = link_to _('collect usage information'), help_page_path('user/admin_area/settings/usage_statistics.md'), class: 'gl-link'
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
variant: :danger,
|
variant: :danger,
|
||||||
dismissible: false,
|
dismissible: false,
|
||||||
is_contained: true,
|
|
||||||
alert_class: 'gl-mb-5' do
|
alert_class: 'gl-mb-5' do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
Someone edited the #{issuable.class.model_name.human.downcase} the same time you did.
|
Someone edited the #{issuable.class.model_name.human.downcase} the same time you did.
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
- if milestone.complete? && milestone.active?
|
- if milestone.complete? && milestone.active?
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
variant: :success,
|
variant: :success,
|
||||||
is_contained: true,
|
|
||||||
alert_data: { testid: 'all-issues-closed-alert' },
|
alert_data: { testid: 'all-issues-closed-alert' },
|
||||||
dismissible: false do
|
dismissible: false do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
title: s_('Webhooks|Webhook was automatically disabled'),
|
title: s_('Webhooks|Webhook was automatically disabled'),
|
||||||
variant: :danger,
|
variant: :danger,
|
||||||
is_contained: true,
|
|
||||||
close_button_class: 'js-close' do
|
close_button_class: 'js-close' do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
= s_('Webhooks|The webhook was triggered more than %{limit} times per minute and is now disabled. To re-enable this webhook, fix the problems shown in %{strong_start}Recent events%{strong_end}, then re-test your settings. %{support_link_start}Contact Support%{support_link_end} if you need help re-enabling your webhook.').html_safe % placeholders
|
= s_('Webhooks|The webhook was triggered more than %{limit} times per minute and is now disabled. To re-enable this webhook, fix the problems shown in %{strong_start}Recent events%{strong_end}, then re-test your settings. %{support_link_start}Contact Support%{support_link_end} if you need help re-enabling your webhook.').html_safe % placeholders
|
||||||
|
@ -21,7 +20,6 @@
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
title: s_('Webhooks|Webhook failed to connect'),
|
title: s_('Webhooks|Webhook failed to connect'),
|
||||||
variant: :danger,
|
variant: :danger,
|
||||||
is_contained: true,
|
|
||||||
close_button_class: 'js-close' do
|
close_button_class: 'js-close' do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
= s_('Webhooks|The webhook failed to connect, and is disabled. To re-enable it, check %{strong_start}Recent events%{strong_end} for error details, then test your settings below.').html_safe % { strong_start: strong_start, strong_end: strong_end }
|
= s_('Webhooks|The webhook failed to connect, and is disabled. To re-enable it, check %{strong_start}Recent events%{strong_end} for error details, then test your settings below.').html_safe % { strong_start: strong_start, strong_end: strong_end }
|
||||||
|
@ -35,7 +33,6 @@
|
||||||
= render 'shared/global_alert',
|
= render 'shared/global_alert',
|
||||||
title: s_('Webhooks|Webhook fails to connect'),
|
title: s_('Webhooks|Webhook fails to connect'),
|
||||||
variant: :warning,
|
variant: :warning,
|
||||||
is_contained: true,
|
|
||||||
close_button_class: 'js-close' do
|
close_button_class: 'js-close' do
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
= s_('Webhooks|The webhook %{help_link_start}failed to connect%{help_link_end}, and will retry in %{retry_time}. To re-enable it, check %{strong_start}Recent events%{strong_end} for error details, then test your settings below.').html_safe % placeholders
|
= s_('Webhooks|The webhook %{help_link_start}failed to connect%{help_link_end}, and will retry in %{retry_time}. To re-enable it, check %{strong_start}Recent events%{strong_end} for error details, then test your settings below.').html_safe % placeholders
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
- add_to_breadcrumbs _('Wiki'), wiki_path(@wiki)
|
- add_to_breadcrumbs _('Wiki'), wiki_path(@wiki)
|
||||||
- breadcrumb_title s_("Wiki|Pages")
|
- breadcrumb_title s_("Wiki|Pages")
|
||||||
- page_title s_("Wiki|Pages"), _("Wiki")
|
- page_title s_("Wiki|Pages"), _("Wiki")
|
||||||
- sort_title = wiki_sort_title(params[:sort])
|
|
||||||
- add_page_specific_style 'page_bundles/wiki'
|
- add_page_specific_style 'page_bundles/wiki'
|
||||||
|
- wiki_sort_options = [{ text: s_("Wiki|Title"), value: 'title', href: wiki_path(@wiki, action: :pages, sort: Wiki::TITLE_ORDER)}, { text: s_("Wiki|Created date"), value: 'created_at', href: wiki_path(@wiki, action: :pages, sort: Wiki::CREATED_AT_ORDER) }]
|
||||||
|
|
||||||
.wiki-page-header.top-area.flex-column.flex-lg-row
|
.wiki-page-header.top-area.flex-column.flex-lg-row
|
||||||
%h3.page-title.gl-flex-grow-1
|
%h3.page-title.gl-flex-grow-1
|
||||||
|
@ -15,14 +15,7 @@
|
||||||
|
|
||||||
.dropdown.inline.wiki-sort-dropdown
|
.dropdown.inline.wiki-sort-dropdown
|
||||||
.btn-group{ role: 'group' }
|
.btn-group{ role: 'group' }
|
||||||
.btn-group{ role: 'group' }
|
= gl_redirect_listbox_tag wiki_sort_options, params[:sort], data: { right: true }
|
||||||
%button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn gl-button btn-default' }
|
|
||||||
= sort_title
|
|
||||||
= sprite_icon('chevron-down')
|
|
||||||
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
|
|
||||||
%li
|
|
||||||
= sortable_item(s_("Wiki|Title"), wiki_path(@wiki, action: :pages, sort: Wiki::TITLE_ORDER), sort_title)
|
|
||||||
= sortable_item(s_("Wiki|Created date"), wiki_path(@wiki, action: :pages, sort: Wiki::CREATED_AT_ORDER), sort_title)
|
|
||||||
= wiki_sort_controls(@wiki, params[:sort], params[:direction])
|
= wiki_sort_controls(@wiki, params[:sort], params[:direction])
|
||||||
|
|
||||||
%ul.wiki-pages-list.content-list
|
%ul.wiki-pages-list.content-list
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
name: personal_project_owner_with_owner_access
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78193
|
||||||
|
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351919
|
||||||
|
milestone: '14.8'
|
||||||
|
type: development
|
||||||
|
group: group::workspace
|
||||||
|
default_enabled: false
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
name: web_ide_primary_edit
|
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35957
|
|
||||||
rollout_issue_url:
|
|
||||||
milestone: '13.3'
|
|
||||||
type: development
|
|
||||||
group: group::editor
|
|
||||||
default_enabled: false
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
|
||||||
|
# for more information on how to write migrations for GitLab.
|
||||||
|
|
||||||
|
class AddUpdatedStateByUserIdToMergeRequestReviewers < Gitlab::Database::Migration[1.0]
|
||||||
|
enable_lock_retries!
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :merge_request_reviewers, :updated_state_by_user_id, :bigint
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddIndexToMergeRequestReviewersUpdatedStateByUserId < Gitlab::Database::Migration[1.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
INDEX_NAME = 'index_on_merge_request_reviewers_updated_state_by_user_id'
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_index :merge_request_reviewers, :updated_state_by_user_id, name: INDEX_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_concurrent_index_by_name :merge_request_reviewers, INDEX_NAME
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddUpdatedStateByUserIdToMergeRequestAssignees < Gitlab::Database::Migration[1.0]
|
||||||
|
enable_lock_retries!
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :merge_request_assignees, :updated_state_by_user_id, :bigint
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddIndexToMergeRequestAssigneesUpdatedStateByUserId < Gitlab::Database::Migration[1.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
INDEX_NAME = 'index_on_merge_request_assignees_updated_state_by_user_id'
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_index :merge_request_assignees, :updated_state_by_user_id, name: INDEX_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_concurrent_index_by_name :merge_request_assignees, INDEX_NAME
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddForeignKeyToUpdatedStateByUserIdToMergeRequestAssignees < Gitlab::Database::Migration[1.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_foreign_key :merge_request_assignees, :users, column: :updated_state_by_user_id, on_delete: :nullify
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
with_lock_retries do
|
||||||
|
remove_foreign_key :merge_request_assignees, column: :updated_state_by_user_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddForeignKeyToUpdatedStateByUserIdToMergeRequestReviewers < Gitlab::Database::Migration[1.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_foreign_key :merge_request_reviewers, :users, column: :updated_state_by_user_id, on_delete: :nullify
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
with_lock_retries do
|
||||||
|
remove_foreign_key :merge_request_reviewers, column: :updated_state_by_user_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
1
db/schema_migrations/20211203160952
Normal file
1
db/schema_migrations/20211203160952
Normal file
|
@ -0,0 +1 @@
|
||||||
|
f25c65dfcb5b7b4a663cc4c792ffd985f6afd3156036485a5a43a791ee799e7b
|
1
db/schema_migrations/20211203161149
Normal file
1
db/schema_migrations/20211203161149
Normal file
|
@ -0,0 +1 @@
|
||||||
|
01482a299a7dac9d3f786f0dbe4650c686911bf788467146d3e9a91eafd0fc32
|
1
db/schema_migrations/20211203161840
Normal file
1
db/schema_migrations/20211203161840
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1b895e979ba2f1696559179c46c000e349da2d1ab94c968dd95103f188425103
|
1
db/schema_migrations/20211203161942
Normal file
1
db/schema_migrations/20211203161942
Normal file
|
@ -0,0 +1 @@
|
||||||
|
62432b2679cafa381671c9555f503867c254a7b3734e10cf634b34998d5fb5a3
|
1
db/schema_migrations/20220105152547
Normal file
1
db/schema_migrations/20220105152547
Normal file
|
@ -0,0 +1 @@
|
||||||
|
0f1ea41fae57710e0e05c9b71a14800394c4c57e37a39e92be49c50120d7d2ee
|
1
db/schema_migrations/20220105153149
Normal file
1
db/schema_migrations/20220105153149
Normal file
|
@ -0,0 +1 @@
|
||||||
|
8194c695a809f2eb29e5033f089c1d20874f61731a4289026f2d550854e7097d
|
|
@ -16236,7 +16236,8 @@ CREATE TABLE merge_request_assignees (
|
||||||
user_id integer NOT NULL,
|
user_id integer NOT NULL,
|
||||||
merge_request_id integer NOT NULL,
|
merge_request_id integer NOT NULL,
|
||||||
created_at timestamp with time zone,
|
created_at timestamp with time zone,
|
||||||
state smallint DEFAULT 0 NOT NULL
|
state smallint DEFAULT 0 NOT NULL,
|
||||||
|
updated_state_by_user_id bigint
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE SEQUENCE merge_request_assignees_id_seq
|
CREATE SEQUENCE merge_request_assignees_id_seq
|
||||||
|
@ -16462,7 +16463,8 @@ CREATE TABLE merge_request_reviewers (
|
||||||
user_id bigint NOT NULL,
|
user_id bigint NOT NULL,
|
||||||
merge_request_id bigint NOT NULL,
|
merge_request_id bigint NOT NULL,
|
||||||
created_at timestamp with time zone NOT NULL,
|
created_at timestamp with time zone NOT NULL,
|
||||||
state smallint DEFAULT 0 NOT NULL
|
state smallint DEFAULT 0 NOT NULL,
|
||||||
|
updated_state_by_user_id bigint
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE SEQUENCE merge_request_reviewers_id_seq
|
CREATE SEQUENCE merge_request_reviewers_id_seq
|
||||||
|
@ -27232,8 +27234,12 @@ CREATE INDEX index_on_label_links_all_columns ON label_links USING btree (target
|
||||||
|
|
||||||
CREATE INDEX index_on_merge_request_assignees_state ON merge_request_assignees USING btree (state) WHERE (state = 2);
|
CREATE INDEX index_on_merge_request_assignees_state ON merge_request_assignees USING btree (state) WHERE (state = 2);
|
||||||
|
|
||||||
|
CREATE INDEX index_on_merge_request_assignees_updated_state_by_user_id ON merge_request_assignees USING btree (updated_state_by_user_id);
|
||||||
|
|
||||||
CREATE INDEX index_on_merge_request_reviewers_state ON merge_request_reviewers USING btree (state) WHERE (state = 2);
|
CREATE INDEX index_on_merge_request_reviewers_state ON merge_request_reviewers USING btree (state) WHERE (state = 2);
|
||||||
|
|
||||||
|
CREATE INDEX index_on_merge_request_reviewers_updated_state_by_user_id ON merge_request_reviewers USING btree (updated_state_by_user_id);
|
||||||
|
|
||||||
CREATE INDEX index_on_merge_requests_for_latest_diffs ON merge_requests USING btree (target_project_id) INCLUDE (id, latest_merge_request_diff_id);
|
CREATE INDEX index_on_merge_requests_for_latest_diffs ON merge_requests USING btree (target_project_id) INCLUDE (id, latest_merge_request_diff_id);
|
||||||
|
|
||||||
COMMENT ON INDEX index_on_merge_requests_for_latest_diffs IS 'Index used to efficiently obtain the oldest merge request for a commit SHA';
|
COMMENT ON INDEX index_on_merge_requests_for_latest_diffs IS 'Index used to efficiently obtain the oldest merge request for a commit SHA';
|
||||||
|
@ -29678,6 +29684,9 @@ ALTER TABLE ONLY epics
|
||||||
ALTER TABLE ONLY ci_pipelines
|
ALTER TABLE ONLY ci_pipelines
|
||||||
ADD CONSTRAINT fk_3d34ab2e06 FOREIGN KEY (pipeline_schedule_id) REFERENCES ci_pipeline_schedules(id) ON DELETE SET NULL;
|
ADD CONSTRAINT fk_3d34ab2e06 FOREIGN KEY (pipeline_schedule_id) REFERENCES ci_pipeline_schedules(id) ON DELETE SET NULL;
|
||||||
|
|
||||||
|
ALTER TABLE ONLY merge_request_reviewers
|
||||||
|
ADD CONSTRAINT fk_3d674b9f23 FOREIGN KEY (updated_state_by_user_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||||
|
|
||||||
ALTER TABLE ONLY ci_pipeline_schedule_variables
|
ALTER TABLE ONLY ci_pipeline_schedule_variables
|
||||||
ADD CONSTRAINT fk_41c35fda51 FOREIGN KEY (pipeline_schedule_id) REFERENCES ci_pipeline_schedules(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_41c35fda51 FOREIGN KEY (pipeline_schedule_id) REFERENCES ci_pipeline_schedules(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
@ -30002,6 +30011,9 @@ ALTER TABLE ONLY merge_request_metrics
|
||||||
ALTER TABLE ONLY dast_profile_schedules
|
ALTER TABLE ONLY dast_profile_schedules
|
||||||
ADD CONSTRAINT fk_aef03d62e5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
|
ADD CONSTRAINT fk_aef03d62e5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||||
|
|
||||||
|
ALTER TABLE ONLY merge_request_assignees
|
||||||
|
ADD CONSTRAINT fk_af036e3261 FOREIGN KEY (updated_state_by_user_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||||
|
|
||||||
ALTER TABLE ONLY analytics_cycle_analytics_group_stages
|
ALTER TABLE ONLY analytics_cycle_analytics_group_stages
|
||||||
ADD CONSTRAINT fk_analytics_cycle_analytics_group_stages_group_value_stream_id FOREIGN KEY (group_value_stream_id) REFERENCES analytics_cycle_analytics_group_value_streams(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_analytics_cycle_analytics_group_stages_group_value_stream_id FOREIGN KEY (group_value_stream_id) REFERENCES analytics_cycle_analytics_group_value_streams(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
# Install GitLab under a relative URL **(FREE SELF)**
|
# Install GitLab under a relative URL **(FREE SELF)**
|
||||||
|
|
||||||
While it is recommended to install GitLab on its own (sub)domain, sometimes
|
While we recommend to install GitLab on its own (sub)domain, sometimes
|
||||||
this is not possible due to a variety of reasons. In that case, GitLab can also
|
this is not possible due to a variety of reasons. In that case, GitLab can also
|
||||||
be installed under a relative URL, for example `https://example.com/gitlab`.
|
be installed under a relative URL, for example `https://example.com/gitlab`.
|
||||||
|
|
||||||
|
@ -19,8 +19,8 @@ first time.
|
||||||
There is no limit to how deeply nested the relative URL can be. For example you
|
There is no limit to how deeply nested the relative URL can be. For example you
|
||||||
could serve GitLab under `/foo/bar/gitlab/git` without any issues.
|
could serve GitLab under `/foo/bar/gitlab/git` without any issues.
|
||||||
|
|
||||||
Note that by changing the URL on an existing GitLab installation, all remote
|
Changing the URL on an existing GitLab installation, changes all remote
|
||||||
URLs will change, so you'll have to manually edit them in any local repository
|
URLs, so you have to manually edit them in any local repository
|
||||||
that points to your GitLab instance.
|
that points to your GitLab instance.
|
||||||
|
|
||||||
The list of configuration files you must change to serve GitLab from a
|
The list of configuration files you must change to serve GitLab from a
|
||||||
|
@ -32,7 +32,7 @@ relative URL is:
|
||||||
- `/home/git/gitlab-shell/config.yml`
|
- `/home/git/gitlab-shell/config.yml`
|
||||||
- `/etc/default/gitlab`
|
- `/etc/default/gitlab`
|
||||||
|
|
||||||
After all the changes you need to recompile the assets and [restart GitLab](../administration/restart_gitlab.md#installations-from-source).
|
After all the changes, you must recompile the assets and [restart GitLab](../administration/restart_gitlab.md#installations-from-source).
|
||||||
|
|
||||||
## Relative URL requirements
|
## Relative URL requirements
|
||||||
|
|
||||||
|
|
|
@ -33,14 +33,27 @@ usernames. A GitLab administrator can configure the GitLab instance to
|
||||||
|
|
||||||
## Project members permissions
|
## Project members permissions
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/219299) in GitLab 14.8, personal namespace owners appear with Owner role in new projects in their namespace. Introduced [with a flag](../administration/feature_flags.md) named `personal_project_owner_with_owner_access`. Disabled by default.
|
||||||
|
|
||||||
|
FLAG:
|
||||||
|
On self-managed GitLab, personal namespace owners appearing with the Owner role in new projects in their namespace is disabled. To make it available,
|
||||||
|
ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `personal_project_owner_with_owner_access`.
|
||||||
|
The feature is not ready for production use.
|
||||||
|
On GitLab.com, this feature is not available.
|
||||||
|
|
||||||
A user's role determines what permissions they have on a project. The Owner role provides all permissions but is
|
A user's role determines what permissions they have on a project. The Owner role provides all permissions but is
|
||||||
available only:
|
available only:
|
||||||
|
|
||||||
- For group owners. The role is inherited for a group's projects.
|
- For group owners. The role is inherited for a group's projects.
|
||||||
- For Administrators.
|
- For Administrators.
|
||||||
|
|
||||||
Personal namespace owners have the same permissions as an Owner, but are displayed with the Maintainer role on projects created in their personal namespace.
|
Personal [namespace](group/index.md#namespaces) owners:
|
||||||
For more information, see [projects members documentation](project/members/index.md).
|
|
||||||
|
- Are displayed as having the Maintainer role on projects in the namespace, but have the same permissions as a user with the Owner role.
|
||||||
|
- (Disabled by default) In GitLab 14.8 and later, for new projects in the namespace, are displayed as having the Owner role.
|
||||||
|
|
||||||
|
For more information about how to manage project members, see
|
||||||
|
[members of a project](project/members/index.md).
|
||||||
|
|
||||||
The following table lists project permissions available for each role:
|
The following table lists project permissions available for each role:
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,11 @@ module Backup
|
||||||
|
|
||||||
DEFAULT_EXCLUDE = 'lost+found'
|
DEFAULT_EXCLUDE = 'lost+found'
|
||||||
|
|
||||||
attr_reader :name, :app_files_dir, :backup_tarball, :excludes, :files_parent_dir
|
attr_reader :name, :backup_tarball, :excludes
|
||||||
|
|
||||||
def initialize(name, app_files_dir, excludes: [])
|
def initialize(name, app_files_dir, excludes: [])
|
||||||
@name = name
|
@name = name
|
||||||
@app_files_dir = File.realpath(app_files_dir)
|
@app_files_dir = app_files_dir
|
||||||
@files_parent_dir = File.realpath(File.join(@app_files_dir, '..'))
|
|
||||||
@backup_files_dir = File.join(Gitlab.config.backup.path, File.basename(@app_files_dir) )
|
|
||||||
@backup_tarball = File.join(Gitlab.config.backup.path, name + '.tar.gz')
|
@backup_tarball = File.join(Gitlab.config.backup.path, name + '.tar.gz')
|
||||||
@excludes = [DEFAULT_EXCLUDE].concat(excludes)
|
@excludes = [DEFAULT_EXCLUDE].concat(excludes)
|
||||||
end
|
end
|
||||||
|
@ -26,7 +24,7 @@ module Backup
|
||||||
FileUtils.rm_f(backup_tarball)
|
FileUtils.rm_f(backup_tarball)
|
||||||
|
|
||||||
if ENV['STRATEGY'] == 'copy'
|
if ENV['STRATEGY'] == 'copy'
|
||||||
cmd = [%w[rsync -a --delete], exclude_dirs(:rsync), %W[#{app_files_dir} #{Gitlab.config.backup.path}]].flatten
|
cmd = [%w[rsync -a --delete], exclude_dirs(:rsync), %W[#{app_files_realpath} #{Gitlab.config.backup.path}]].flatten
|
||||||
output, status = Gitlab::Popen.popen(cmd)
|
output, status = Gitlab::Popen.popen(cmd)
|
||||||
|
|
||||||
# Retry if rsync source files vanish
|
# Retry if rsync source files vanish
|
||||||
|
@ -40,11 +38,11 @@ module Backup
|
||||||
raise_custom_error
|
raise_custom_error
|
||||||
end
|
end
|
||||||
|
|
||||||
tar_cmd = [tar, exclude_dirs(:tar), %W[-C #{@backup_files_dir} -cf - .]].flatten
|
tar_cmd = [tar, exclude_dirs(:tar), %W[-C #{backup_files_realpath} -cf - .]].flatten
|
||||||
status_list, output = run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
|
status_list, output = run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
|
||||||
FileUtils.rm_rf(@backup_files_dir)
|
FileUtils.rm_rf(backup_files_realpath)
|
||||||
else
|
else
|
||||||
tar_cmd = [tar, exclude_dirs(:tar), %W[-C #{app_files_dir} -cf - .]].flatten
|
tar_cmd = [tar, exclude_dirs(:tar), %W[-C #{app_files_realpath} -cf - .]].flatten
|
||||||
status_list, output = run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
|
status_list, output = run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -56,7 +54,7 @@ module Backup
|
||||||
def restore
|
def restore
|
||||||
backup_existing_files_dir
|
backup_existing_files_dir
|
||||||
|
|
||||||
cmd_list = [%w[gzip -cd], %W[#{tar} --unlink-first --recursive-unlink -C #{app_files_dir} -xf -]]
|
cmd_list = [%w[gzip -cd], %W[#{tar} --unlink-first --recursive-unlink -C #{app_files_realpath} -xf -]]
|
||||||
status_list, output = run_pipeline!(cmd_list, in: backup_tarball)
|
status_list, output = run_pipeline!(cmd_list, in: backup_tarball)
|
||||||
unless pipeline_succeeded?(gzip_status: status_list[0], tar_status: status_list[1], output: output)
|
unless pipeline_succeeded?(gzip_status: status_list[0], tar_status: status_list[1], output: output)
|
||||||
raise Backup::Error, "Restore operation failed: #{output}"
|
raise Backup::Error, "Restore operation failed: #{output}"
|
||||||
|
@ -78,17 +76,17 @@ module Backup
|
||||||
|
|
||||||
def backup_existing_files_dir
|
def backup_existing_files_dir
|
||||||
timestamped_files_path = File.join(Gitlab.config.backup.path, "tmp", "#{name}.#{Time.now.to_i}")
|
timestamped_files_path = File.join(Gitlab.config.backup.path, "tmp", "#{name}.#{Time.now.to_i}")
|
||||||
if File.exist?(app_files_dir)
|
if File.exist?(app_files_realpath)
|
||||||
# Move all files in the existing repos directory except . and .. to
|
# Move all files in the existing repos directory except . and .. to
|
||||||
# repositories.old.<timestamp> directory
|
# repositories.old.<timestamp> directory
|
||||||
FileUtils.mkdir_p(timestamped_files_path, mode: 0700)
|
FileUtils.mkdir_p(timestamped_files_path, mode: 0700)
|
||||||
files = Dir.glob(File.join(app_files_dir, "*"), File::FNM_DOTMATCH) - [File.join(app_files_dir, "."), File.join(app_files_dir, "..")]
|
files = Dir.glob(File.join(app_files_realpath, "*"), File::FNM_DOTMATCH) - [File.join(app_files_realpath, "."), File.join(app_files_realpath, "..")]
|
||||||
begin
|
begin
|
||||||
FileUtils.mv(files, timestamped_files_path)
|
FileUtils.mv(files, timestamped_files_path)
|
||||||
rescue Errno::EACCES
|
rescue Errno::EACCES
|
||||||
access_denied_error(app_files_dir)
|
access_denied_error(app_files_realpath)
|
||||||
rescue Errno::EBUSY
|
rescue Errno::EBUSY
|
||||||
resource_busy_error(app_files_dir)
|
resource_busy_error(app_files_realpath)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -141,7 +139,7 @@ module Backup
|
||||||
if s == DEFAULT_EXCLUDE
|
if s == DEFAULT_EXCLUDE
|
||||||
'--exclude=' + s
|
'--exclude=' + s
|
||||||
elsif fmt == :rsync
|
elsif fmt == :rsync
|
||||||
'--exclude=/' + File.join(File.basename(app_files_dir), s)
|
'--exclude=/' + File.join(File.basename(app_files_realpath), s)
|
||||||
elsif fmt == :tar
|
elsif fmt == :tar
|
||||||
'--exclude=./' + s
|
'--exclude=./' + s
|
||||||
end
|
end
|
||||||
|
@ -149,7 +147,17 @@ module Backup
|
||||||
end
|
end
|
||||||
|
|
||||||
def raise_custom_error
|
def raise_custom_error
|
||||||
raise FileBackupError.new(app_files_dir, backup_tarball)
|
raise FileBackupError.new(app_files_realpath, backup_tarball)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def app_files_realpath
|
||||||
|
@app_files_realpath ||= File.realpath(@app_files_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
def backup_files_realpath
|
||||||
|
@backup_files_realpath ||= File.join(Gitlab.config.backup.path, File.basename(@app_files_dir) )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ module Gitlab
|
||||||
user.projects_with_active_memberships.select_for_project_authorization,
|
user.projects_with_active_memberships.select_for_project_authorization,
|
||||||
|
|
||||||
# The personal projects of the user.
|
# The personal projects of the user.
|
||||||
user.personal_projects.select_as_maintainer_for_project_authorization,
|
user.personal_projects.select_project_owner_for_project_authorization,
|
||||||
|
|
||||||
# Projects that belong directly to any of the groups the user has
|
# Projects that belong directly to any of the groups the user has
|
||||||
# access to.
|
# access to.
|
||||||
|
|
|
@ -38560,10 +38560,8 @@ msgstr ""
|
||||||
msgid "Trending"
|
msgid "Trending"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Trials|%{planName} Trial %{enDash} %{num} day left"
|
msgid "Trials|%{planName} Trial"
|
||||||
msgid_plural "Trials|%{planName} Trial %{enDash} %{num} days left"
|
msgstr ""
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
msgid "Trials|Compare all plans"
|
msgid "Trials|Compare all plans"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -38571,6 +38569,9 @@ msgstr ""
|
||||||
msgid "Trials|Create a new group to start your GitLab Ultimate trial."
|
msgid "Trials|Create a new group to start your GitLab Ultimate trial."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Trials|Day %{daysUsed}/%{duration}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Trials|Go back to GitLab"
|
msgid "Trials|Go back to GitLab"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -665,7 +665,7 @@ RSpec.describe Projects::ProjectMembersController do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not create a member' do
|
it 'creates a member' do
|
||||||
expect do
|
expect do
|
||||||
post :create, params: {
|
post :create, params: {
|
||||||
user_ids: stranger.id,
|
user_ids: stranger.id,
|
||||||
|
@ -673,7 +673,9 @@ RSpec.describe Projects::ProjectMembersController do
|
||||||
access_level: Member::OWNER,
|
access_level: Member::OWNER,
|
||||||
project_id: project
|
project_id: project
|
||||||
}
|
}
|
||||||
end.to change { project.members.count }.by(0)
|
end.to change { project.members.count }.by(1)
|
||||||
|
|
||||||
|
expect(project.team_members).to include(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require "spec_helper"
|
require "spec_helper"
|
||||||
|
|
||||||
RSpec.describe 'Project wikis' do
|
RSpec.describe 'Project wikis', :js do
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
|
|
||||||
let(:wiki) { create(:project_wiki, user: user, project: project) }
|
let(:wiki) { create(:project_wiki, user: user, project: project) }
|
||||||
|
|
|
@ -11,21 +11,40 @@ RSpec.describe Projects::Members::EffectiveAccessLevelFinder, '#execute' do
|
||||||
context 'for a personal project' do
|
context 'for a personal project' do
|
||||||
let_it_be(:project) { create(:project) }
|
let_it_be(:project) { create(:project) }
|
||||||
|
|
||||||
shared_examples_for 'includes access level of the owner of the project as Maintainer' do
|
shared_examples_for 'includes access level of the owner of the project' do
|
||||||
it 'includes access level of the owner of the project as Maintainer' do
|
context 'when personal_project_owner_with_owner_access feature flag is enabled' do
|
||||||
expect(subject).to(
|
it 'includes access level of the owner of the project as Owner' do
|
||||||
contain_exactly(
|
expect(subject).to(
|
||||||
hash_including(
|
contain_exactly(
|
||||||
'user_id' => project.namespace.owner.id,
|
hash_including(
|
||||||
'access_level' => Gitlab::Access::MAINTAINER
|
'user_id' => project.namespace.owner.id,
|
||||||
|
'access_level' => Gitlab::Access::OWNER
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when personal_project_owner_with_owner_access feature flag is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(personal_project_owner_with_owner_access: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes access level of the owner of the project as Maintainer' do
|
||||||
|
expect(subject).to(
|
||||||
|
contain_exactly(
|
||||||
|
hash_including(
|
||||||
|
'user_id' => project.namespace.owner.id,
|
||||||
|
'access_level' => Gitlab::Access::MAINTAINER
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the project owner is a member of the project' do
|
context 'when the project owner is a member of the project' do
|
||||||
it_behaves_like 'includes access level of the owner of the project as Maintainer'
|
it_behaves_like 'includes access level of the owner of the project'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the project owner is not explicitly a member of the project' do
|
context 'when the project owner is not explicitly a member of the project' do
|
||||||
|
@ -33,7 +52,7 @@ RSpec.describe Projects::Members::EffectiveAccessLevelFinder, '#execute' do
|
||||||
project.members.find_by(user_id: project.namespace.owner.id).destroy!
|
project.members.find_by(user_id: project.namespace.owner.id).destroy!
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'includes access level of the owner of the project as Maintainer'
|
it_behaves_like 'includes access level of the owner of the project'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -84,17 +103,32 @@ RSpec.describe Projects::Members::EffectiveAccessLevelFinder, '#execute' do
|
||||||
|
|
||||||
context 'for a project within a group' do
|
context 'for a project within a group' do
|
||||||
context 'project in a root group' do
|
context 'project in a root group' do
|
||||||
it 'includes access levels of users who are direct members of the parent group' do
|
context 'includes access levels of users who are direct members of the parent group' do
|
||||||
group_member = create(:group_member, :developer, source: group)
|
it 'when access level is developer' do
|
||||||
|
group_member = create(:group_member, :developer, source: group)
|
||||||
|
|
||||||
expect(subject).to(
|
expect(subject).to(
|
||||||
include(
|
include(
|
||||||
hash_including(
|
hash_including(
|
||||||
'user_id' => group_member.user.id,
|
'user_id' => group_member.user.id,
|
||||||
'access_level' => Gitlab::Access::DEVELOPER
|
'access_level' => Gitlab::Access::DEVELOPER
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
end
|
||||||
|
|
||||||
|
it 'when access level is owner' do
|
||||||
|
group_member = create(:group_member, :owner, source: group)
|
||||||
|
|
||||||
|
expect(subject).to(
|
||||||
|
include(
|
||||||
|
hash_including(
|
||||||
|
'user_id' => group_member.user.id,
|
||||||
|
'access_level' => Gitlab::Access::OWNER
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -54,42 +54,6 @@ RSpec.describe BlobHelper do
|
||||||
|
|
||||||
expect(Capybara.string(link_with_mr).find_link('Edit')[:href]).to eq("/#{project.full_path}/-/edit/master/README.md?mr_id=10")
|
expect(Capybara.string(link_with_mr).find_link('Edit')[:href]).to eq("/#{project.full_path}/-/edit/master/README.md?mr_id=10")
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when edit is the primary button' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(web_ide_primary_edit: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is rendered as primary' do
|
|
||||||
expect(link).not_to match(/btn-inverted/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'passes on primary tracking attributes' do
|
|
||||||
parsed_link = Capybara.string(link).find_link('Edit')
|
|
||||||
|
|
||||||
expect(parsed_link[:'data-track-action']).to eq("click_edit")
|
|
||||||
expect(parsed_link[:'data-track-label']).to eq("edit")
|
|
||||||
expect(parsed_link[:'data-track-property']).to eq(nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when Web IDE is the primary button' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(web_ide_primary_edit: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is rendered as inverted' do
|
|
||||||
expect(link).to match(/btn-inverted/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'passes on secondary tracking attributes' do
|
|
||||||
parsed_link = Capybara.string(link).find_link('Edit')
|
|
||||||
|
|
||||||
expect(parsed_link[:'data-track-action']).to eq("click_edit")
|
|
||||||
expect(parsed_link[:'data-track-label']).to eq("edit")
|
|
||||||
expect(parsed_link[:'data-track-property']).to eq("secondary")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#relative_raw_path" do
|
describe "#relative_raw_path" do
|
||||||
|
@ -324,63 +288,6 @@ RSpec.describe BlobHelper do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe `#ide_edit_button` do
|
|
||||||
let_it_be(:namespace) { create(:namespace, name: 'gitlab') }
|
|
||||||
let_it_be(:project) { create(:project, :repository, namespace: namespace) }
|
|
||||||
let_it_be(:current_user) { create(:user) }
|
|
||||||
|
|
||||||
let(:can_push_code) { true }
|
|
||||||
let(:blob) { project.repository.blob_at('refs/heads/master', 'README.md') }
|
|
||||||
|
|
||||||
subject(:link) { helper.ide_edit_button(project, 'master', 'README.md', blob: blob) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(helper).to receive(:current_user).and_return(current_user)
|
|
||||||
allow(helper).to receive(:can?).with(current_user, :push_code, project).and_return(can_push_code)
|
|
||||||
allow(helper).to receive(:can_collaborate_with_project?).and_return(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a link with a Web IDE route' do
|
|
||||||
expect(Capybara.string(link).find_link('Web IDE')[:href]).to eq("/-/ide/project/#{project.full_path}/edit/master/-/README.md")
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when edit is the primary button' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(web_ide_primary_edit: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is rendered as inverted' do
|
|
||||||
expect(link).to match(/btn-inverted/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'passes on secondary tracking attributes' do
|
|
||||||
parsed_link = Capybara.string(link).find_link('Web IDE')
|
|
||||||
|
|
||||||
expect(parsed_link[:'data-track-action']).to eq("click_edit_ide")
|
|
||||||
expect(parsed_link[:'data-track-label']).to eq("web_ide")
|
|
||||||
expect(parsed_link[:'data-track-property']).to eq("secondary")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when Web IDE is the primary button' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(web_ide_primary_edit: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is rendered as primary' do
|
|
||||||
expect(link).not_to match(/btn-inverted/)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'passes on primary tracking attributes' do
|
|
||||||
parsed_link = Capybara.string(link).find_link('Web IDE')
|
|
||||||
|
|
||||||
expect(parsed_link[:'data-track-action']).to eq("click_edit_ide")
|
|
||||||
expect(parsed_link[:'data-track-label']).to eq("web_ide")
|
|
||||||
expect(parsed_link[:'data-track-property']).to eq(nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#ide_edit_path' do
|
describe '#ide_edit_path' do
|
||||||
let(:project) { create(:project) }
|
let(:project) { create(:project) }
|
||||||
let(:current_user) { create(:user) }
|
let(:current_user) { create(:user) }
|
||||||
|
|
|
@ -7,16 +7,6 @@ RSpec.describe Backup::Artifacts do
|
||||||
|
|
||||||
subject(:backup) { described_class.new(progress) }
|
subject(:backup) { described_class.new(progress) }
|
||||||
|
|
||||||
describe '#initialize' do
|
|
||||||
it 'uses the correct upload dir' do
|
|
||||||
Dir.mktmpdir do |tmpdir|
|
|
||||||
allow(JobArtifactUploader).to receive(:root) { "#{tmpdir}" }
|
|
||||||
|
|
||||||
expect(backup.app_files_dir).to eq("#{File.realpath(tmpdir)}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#dump' do
|
describe '#dump' do
|
||||||
before do
|
before do
|
||||||
allow(File).to receive(:realpath).with('/var/gitlab-artifacts').and_return('/var/gitlab-artifacts')
|
allow(File).to receive(:realpath).with('/var/gitlab-artifacts').and_return('/var/gitlab-artifacts')
|
||||||
|
@ -24,10 +14,6 @@ RSpec.describe Backup::Artifacts do
|
||||||
allow(JobArtifactUploader).to receive(:root) { '/var/gitlab-artifacts' }
|
allow(JobArtifactUploader).to receive(:root) { '/var/gitlab-artifacts' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'uses the correct artifact dir' do
|
|
||||||
expect(backup.app_files_dir).to eq('/var/gitlab-artifacts')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'excludes tmp from backup tar' do
|
it 'excludes tmp from backup tar' do
|
||||||
expect(backup).to receive(:tar).and_return('blabla-tar')
|
expect(backup).to receive(:tar).and_return('blabla-tar')
|
||||||
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/gitlab-artifacts -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
|
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/gitlab-artifacts -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
|
||||||
|
|
|
@ -16,7 +16,6 @@ RSpec.describe Backup::Lfs do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'uses the correct lfs dir in tar command', :aggregate_failures do
|
it 'uses the correct lfs dir in tar command', :aggregate_failures do
|
||||||
expect(backup.app_files_dir).to eq('/var/lfs-objects')
|
|
||||||
expect(backup).to receive(:tar).and_return('blabla-tar')
|
expect(backup).to receive(:tar).and_return('blabla-tar')
|
||||||
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found -C /var/lfs-objects -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
|
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found -C /var/lfs-objects -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
|
||||||
expect(backup).to receive(:pipeline_succeeded?).and_return(true)
|
expect(backup).to receive(:pipeline_succeeded?).and_return(true)
|
||||||
|
|
|
@ -12,11 +12,6 @@ RSpec.describe Backup::Manager do
|
||||||
before do
|
before do
|
||||||
allow(progress).to receive(:puts)
|
allow(progress).to receive(:puts)
|
||||||
allow(progress).to receive(:print)
|
allow(progress).to receive(:print)
|
||||||
FileUtils.mkdir_p('tmp/tests/public/uploads')
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
FileUtils.rm_rf('tmp/tests/public/uploads', secure: true)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#pack' do
|
describe '#pack' do
|
||||||
|
|
|
@ -17,7 +17,6 @@ RSpec.shared_examples 'backup object' do |setting|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'uses the correct storage dir in tar command and excludes tmp', :aggregate_failures do
|
it 'uses the correct storage dir in tar command and excludes tmp', :aggregate_failures do
|
||||||
expect(backup.app_files_dir).to eq(backup_path)
|
|
||||||
expect(backup).to receive(:tar).and_return('blabla-tar')
|
expect(backup).to receive(:tar).and_return('blabla-tar')
|
||||||
expect(backup).to receive(:run_pipeline!).with([%W(blabla-tar --exclude=lost+found --exclude=./tmp -C #{backup_path} -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
|
expect(backup).to receive(:run_pipeline!).with([%W(blabla-tar --exclude=lost+found --exclude=./tmp -C #{backup_path} -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
|
||||||
expect(backup).to receive(:pipeline_succeeded?).and_return(true)
|
expect(backup).to receive(:pipeline_succeeded?).and_return(true)
|
||||||
|
|
|
@ -13,12 +13,6 @@ RSpec.describe Backup::Pages do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#dump' do
|
describe '#dump' do
|
||||||
it 'uses the correct pages dir' do
|
|
||||||
allow(Gitlab.config.pages).to receive(:path) { '/var/gitlab-pages' }
|
|
||||||
|
|
||||||
expect(subject.app_files_dir).to eq('/var/gitlab-pages')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'excludes tmp from backup tar' do
|
it 'excludes tmp from backup tar' do
|
||||||
allow(Gitlab.config.pages).to receive(:path) { '/var/gitlab-pages' }
|
allow(Gitlab.config.pages).to receive(:path) { '/var/gitlab-pages' }
|
||||||
|
|
||||||
|
|
|
@ -7,18 +7,6 @@ RSpec.describe Backup::Uploads do
|
||||||
|
|
||||||
subject(:backup) { described_class.new(progress) }
|
subject(:backup) { described_class.new(progress) }
|
||||||
|
|
||||||
describe '#initialize' do
|
|
||||||
it 'uses the correct upload dir' do
|
|
||||||
Dir.mktmpdir do |tmpdir|
|
|
||||||
FileUtils.mkdir_p("#{tmpdir}/uploads")
|
|
||||||
|
|
||||||
allow(Gitlab.config.uploads).to receive(:storage_path) { tmpdir }
|
|
||||||
|
|
||||||
expect(backup.app_files_dir).to eq("#{File.realpath(tmpdir)}/uploads")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#dump' do
|
describe '#dump' do
|
||||||
before do
|
before do
|
||||||
allow(File).to receive(:realpath).and_call_original
|
allow(File).to receive(:realpath).and_call_original
|
||||||
|
@ -27,10 +15,6 @@ RSpec.describe Backup::Uploads do
|
||||||
allow(Gitlab.config.uploads).to receive(:storage_path) { '/var' }
|
allow(Gitlab.config.uploads).to receive(:storage_path) { '/var' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'uses the correct upload dir' do
|
|
||||||
expect(backup.app_files_dir).to eq('/var/uploads')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'excludes tmp from backup tar' do
|
it 'excludes tmp from backup tar' do
|
||||||
expect(backup).to receive(:tar).and_return('blabla-tar')
|
expect(backup).to receive(:tar).and_return('blabla-tar')
|
||||||
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/uploads -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
|
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/uploads -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
|
||||||
|
|
|
@ -34,12 +34,28 @@ RSpec.describe Gitlab::ProjectAuthorizations do
|
||||||
.to include(owned_project.id, other_project.id, group_project.id)
|
.to include(owned_project.id, other_project.id, group_project.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes the correct access levels' do
|
context 'when personal_project_owner_with_owner_access feature flag is enabled' do
|
||||||
mapping = map_access_levels(authorizations)
|
it 'includes the correct access levels' do
|
||||||
|
mapping = map_access_levels(authorizations)
|
||||||
|
|
||||||
expect(mapping[owned_project.id]).to eq(Gitlab::Access::MAINTAINER)
|
expect(mapping[owned_project.id]).to eq(Gitlab::Access::OWNER)
|
||||||
expect(mapping[other_project.id]).to eq(Gitlab::Access::REPORTER)
|
expect(mapping[other_project.id]).to eq(Gitlab::Access::REPORTER)
|
||||||
expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
|
expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when personal_project_owner_with_owner_access feature flag is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(personal_project_owner_with_owner_access: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes the correct access levels' do
|
||||||
|
mapping = map_access_levels(authorizations)
|
||||||
|
|
||||||
|
expect(mapping[owned_project.id]).to eq(Gitlab::Access::MAINTAINER)
|
||||||
|
expect(mapping[other_project.id]).to eq(Gitlab::Access::REPORTER)
|
||||||
|
expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -51,4 +51,24 @@ RSpec.describe MergeRequestAssignee do
|
||||||
|
|
||||||
it { is_expected.to have_attributes(state: 'reviewed') }
|
it { is_expected.to have_attributes(state: 'reviewed') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#attention_requested_by' do
|
||||||
|
let(:current_user) { create(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
subject.update!(updated_state_by: current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'attention requested' do
|
||||||
|
it { expect(subject.attention_requested_by).to eq(current_user) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'attention requested' do
|
||||||
|
before do
|
||||||
|
subject.update!(state: :reviewed)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(subject.attention_requested_by).to eq(nil) }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,4 +25,24 @@ RSpec.describe MergeRequestReviewer do
|
||||||
it { is_expected.to belong_to(:merge_request).class_name('MergeRequest') }
|
it { is_expected.to belong_to(:merge_request).class_name('MergeRequest') }
|
||||||
it { is_expected.to belong_to(:reviewer).class_name('User').inverse_of(:merge_request_reviewers) }
|
it { is_expected.to belong_to(:reviewer).class_name('User').inverse_of(:merge_request_reviewers) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#attention_requested_by' do
|
||||||
|
let(:current_user) { create(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
subject.update!(updated_state_by: current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'attention requested' do
|
||||||
|
it { expect(subject.attention_requested_by).to eq(current_user) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'attention requested' do
|
||||||
|
before do
|
||||||
|
subject.update!(state: :reviewed)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(subject.attention_requested_by).to eq(nil) }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5109,4 +5109,34 @@ RSpec.describe MergeRequest, factory_default: :keep do
|
||||||
let!(:model) { create(:merge_request, head_pipeline: parent) }
|
let!(:model) { create(:merge_request, head_pipeline: parent) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#merge_request_reviewers_with' do
|
||||||
|
let_it_be(:reviewer1) { create(:user) }
|
||||||
|
let_it_be(:reviewer2) { create(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
subject.update!(reviewers: [reviewer1, reviewer2])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns reviewers' do
|
||||||
|
reviewers = subject.merge_request_reviewers_with([reviewer1.id])
|
||||||
|
|
||||||
|
expect(reviewers).to match_array([subject.merge_request_reviewers[0]])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#merge_request_assignees_with' do
|
||||||
|
let_it_be(:assignee1) { create(:user) }
|
||||||
|
let_it_be(:assignee2) { create(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
subject.update!(assignees: [assignee1, assignee2])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns assignees' do
|
||||||
|
assignees = subject.merge_request_assignees_with([assignee1.id])
|
||||||
|
|
||||||
|
expect(assignees).to match_array([subject.merge_request_assignees[0]])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -225,7 +225,7 @@ RSpec.describe ProjectTeam do
|
||||||
let_it_be(:maintainer) { create(:user) }
|
let_it_be(:maintainer) { create(:user) }
|
||||||
let_it_be(:developer) { create(:user) }
|
let_it_be(:developer) { create(:user) }
|
||||||
let_it_be(:guest) { create(:user) }
|
let_it_be(:guest) { create(:user) }
|
||||||
let_it_be(:project) { create(:project, namespace: maintainer.namespace) }
|
let_it_be(:project) { create(:project, group: create(:group)) }
|
||||||
let_it_be(:access_levels) { [Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER] }
|
let_it_be(:access_levels) { [Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER] }
|
||||||
|
|
||||||
subject(:members_with_access_levels) { project.team.members_with_access_levels(access_levels) }
|
subject(:members_with_access_levels) { project.team.members_with_access_levels(access_levels) }
|
||||||
|
|
|
@ -3717,7 +3717,7 @@ RSpec.describe User do
|
||||||
|
|
||||||
context 'with min_access_level' do
|
context 'with min_access_level' do
|
||||||
let!(:user) { create(:user) }
|
let!(:user) { create(:user) }
|
||||||
let!(:project) { create(:project, :private, namespace: user.namespace) }
|
let!(:project) { create(:project, :private, group: create(:group)) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.add_developer(user)
|
project.add_developer(user)
|
||||||
|
|
|
@ -675,13 +675,30 @@ RSpec.describe API::Members do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'adding owner to project' do
|
context 'adding owner to project' do
|
||||||
it 'returns 403' do
|
context 'when personal_project_owner_with_owner_access feature flag is enabled' do
|
||||||
expect do
|
it 'returns created status' do
|
||||||
post api("/projects/#{project.id}/members", maintainer),
|
expect do
|
||||||
params: { user_id: stranger.id, access_level: Member::OWNER }
|
post api("/projects/#{project.id}/members", maintainer),
|
||||||
|
params: { user_id: stranger.id, access_level: Member::OWNER }
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:bad_request)
|
expect(response).to have_gitlab_http_status(:created)
|
||||||
end.not_to change { project.members.count }
|
end.to change { project.members.count }.by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when personal_project_owner_with_owner_access feature flag is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(personal_project_owner_with_owner_access: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns created status' do
|
||||||
|
expect do
|
||||||
|
post api("/projects/#{project.id}/members", maintainer),
|
||||||
|
params: { user_id: stranger.id, access_level: Member::OWNER }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:bad_request)
|
||||||
|
end.not_to change { project.members.count }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
|
||||||
it 'is called' do
|
it 'is called' do
|
||||||
ProjectAuthorization.delete_all
|
ProjectAuthorization.delete_all
|
||||||
|
|
||||||
expect(callback).to receive(:call).with(project.id, Gitlab::Access::MAINTAINER).once
|
expect(callback).to receive(:call).with(project.id, Gitlab::Access::OWNER).once
|
||||||
|
|
||||||
service.execute
|
service.execute
|
||||||
end
|
end
|
||||||
|
@ -60,20 +60,20 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
|
||||||
|
|
||||||
to_be_removed = [project2.id]
|
to_be_removed = [project2.id]
|
||||||
to_be_added = [
|
to_be_added = [
|
||||||
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
|
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
|
||||||
]
|
]
|
||||||
|
|
||||||
expect(service.execute).to eq([to_be_removed, to_be_added])
|
expect(service.execute).to eq([to_be_removed, to_be_added])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'finds duplicate entries that has to be removed' do
|
it 'finds duplicate entries that has to be removed' do
|
||||||
[Gitlab::Access::MAINTAINER, Gitlab::Access::REPORTER].each do |access_level|
|
[Gitlab::Access::OWNER, Gitlab::Access::REPORTER].each do |access_level|
|
||||||
user.project_authorizations.create!(project: project, access_level: access_level)
|
user.project_authorizations.create!(project: project, access_level: access_level)
|
||||||
end
|
end
|
||||||
|
|
||||||
to_be_removed = [project.id]
|
to_be_removed = [project.id]
|
||||||
to_be_added = [
|
to_be_added = [
|
||||||
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
|
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
|
||||||
]
|
]
|
||||||
|
|
||||||
expect(service.execute).to eq([to_be_removed, to_be_added])
|
expect(service.execute).to eq([to_be_removed, to_be_added])
|
||||||
|
@ -85,7 +85,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
|
||||||
|
|
||||||
to_be_removed = [project.id]
|
to_be_removed = [project.id]
|
||||||
to_be_added = [
|
to_be_added = [
|
||||||
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
|
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
|
||||||
]
|
]
|
||||||
|
|
||||||
expect(service.execute).to eq([to_be_removed, to_be_added])
|
expect(service.execute).to eq([to_be_removed, to_be_added])
|
||||||
|
@ -143,16 +143,16 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets the keys to the project IDs' do
|
it 'sets the keys to the project IDs' do
|
||||||
expect(hash.keys).to eq([project.id])
|
expect(hash.keys).to match_array([project.id])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets the values to the access levels' do
|
it 'sets the values to the access levels' do
|
||||||
expect(hash.values).to eq([Gitlab::Access::MAINTAINER])
|
expect(hash.values).to match_array([Gitlab::Access::OWNER])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'personal projects' do
|
context 'personal projects' do
|
||||||
it 'includes the project with the right access level' do
|
it 'includes the project with the right access level' do
|
||||||
expect(hash[project.id]).to eq(Gitlab::Access::MAINTAINER)
|
expect(hash[project.id]).to eq(Gitlab::Access::OWNER)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
|
||||||
value = hash.values[0]
|
value = hash.values[0]
|
||||||
|
|
||||||
expect(value.project_id).to eq(project.id)
|
expect(value.project_id).to eq(project.id)
|
||||||
expect(value.access_level).to eq(Gitlab::Access::MAINTAINER)
|
expect(value.access_level).to eq(Gitlab::Access::OWNER)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -267,7 +267,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes the access level for every row' do
|
it 'includes the access level for every row' do
|
||||||
expect(row.access_level).to eq(Gitlab::Access::MAINTAINER)
|
expect(row.access_level).to eq(Gitlab::Access::OWNER)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -283,7 +283,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
|
||||||
rows = service.fresh_authorizations.to_a
|
rows = service.fresh_authorizations.to_a
|
||||||
|
|
||||||
expect(rows.length).to eq(1)
|
expect(rows.length).to eq(1)
|
||||||
expect(rows.first.access_level).to eq(Gitlab::Access::MAINTAINER)
|
expect(rows.first.access_level).to eq(Gitlab::Access::OWNER)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'every returned row' do
|
context 'every returned row' do
|
||||||
|
@ -294,7 +294,7 @@ RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes the access level' do
|
it 'includes the access level' do
|
||||||
expect(row.access_level).to eq(Gitlab::Access::MAINTAINER)
|
expect(row.access_level).to eq(Gitlab::Access::OWNER)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,8 +9,8 @@ RSpec.describe Members::Projects::CreatorService do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.access_levels' do
|
describe '.access_levels' do
|
||||||
it 'returns Gitlab::Access.sym_options' do
|
it 'returns Gitlab::Access.sym_options_with_owner' do
|
||||||
expect(described_class.access_levels).to eq(Gitlab::Access.sym_options)
|
expect(described_class.access_levels).to eq(Gitlab::Access.sym_options_with_owner)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -95,6 +95,12 @@ RSpec.describe MergeRequests::HandleAssigneesChangeService do
|
||||||
execute
|
execute
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'updates attention requested by of assignee' do
|
||||||
|
execute
|
||||||
|
|
||||||
|
expect(merge_request.find_assignee(assignee).updated_state_by).to eq(user)
|
||||||
|
end
|
||||||
|
|
||||||
it 'tracks users assigned event' do
|
it 'tracks users assigned event' do
|
||||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||||
.to receive(:track_users_assigned_to_mr).once.with(users: [assignee])
|
.to receive(:track_users_assigned_to_mr).once.with(users: [assignee])
|
||||||
|
|
|
@ -59,6 +59,13 @@ RSpec.describe MergeRequests::ToggleAttentionRequestedService do
|
||||||
expect(reviewer.state).to eq 'attention_requested'
|
expect(reviewer.state).to eq 'attention_requested'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'adds who toggled attention' do
|
||||||
|
service.execute
|
||||||
|
reviewer.reload
|
||||||
|
|
||||||
|
expect(reviewer.updated_state_by).to eq current_user
|
||||||
|
end
|
||||||
|
|
||||||
it 'creates a new todo for the reviewer' do
|
it 'creates a new todo for the reviewer' do
|
||||||
expect(todo_service).to receive(:create_attention_requested_todo).with(merge_request, current_user, user)
|
expect(todo_service).to receive(:create_attention_requested_todo).with(merge_request, current_user, user)
|
||||||
|
|
||||||
|
|
|
@ -215,6 +215,14 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
|
||||||
|
|
||||||
MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
|
MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'updates attention requested by of reviewer' do
|
||||||
|
opts[:reviewers] = [user2]
|
||||||
|
|
||||||
|
MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
|
||||||
|
|
||||||
|
expect(merge_request.find_reviewer(user2).updated_state_by).to eq(user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when reviewers did not change' do
|
context 'when reviewers did not change' do
|
||||||
|
|
|
@ -3312,7 +3312,7 @@ RSpec.describe NotificationService, :mailer do
|
||||||
describe "##{sym}" do
|
describe "##{sym}" do
|
||||||
subject(:notify!) { notification.send(sym, domain) }
|
subject(:notify!) { notification.send(sym, domain) }
|
||||||
|
|
||||||
it 'emails current watching maintainers' do
|
it 'emails current watching maintainers and owners' do
|
||||||
expect(Notify).to receive(:"#{sym}_email").at_least(:once).and_call_original
|
expect(Notify).to receive(:"#{sym}_email").at_least(:once).and_call_original
|
||||||
|
|
||||||
notify!
|
notify!
|
||||||
|
@ -3410,7 +3410,7 @@ RSpec.describe NotificationService, :mailer do
|
||||||
reset_delivered_emails!
|
reset_delivered_emails!
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'emails current watching maintainers' do
|
it 'emails current watching maintainers and owners' do
|
||||||
notification.remote_mirror_update_failed(remote_mirror)
|
notification.remote_mirror_update_failed(remote_mirror)
|
||||||
|
|
||||||
should_only_email(u_maintainer1, u_maintainer2, u_owner)
|
should_only_email(u_maintainer1, u_maintainer2, u_owner)
|
||||||
|
|
|
@ -116,14 +116,34 @@ RSpec.describe Projects::CreateService, '#execute' do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'user namespace' do
|
context 'user namespace' do
|
||||||
it 'creates a project in user namespace' do
|
context 'when personal_project_owner_with_owner_access feature flag is enabled' do
|
||||||
project = create_project(user, opts)
|
it 'creates a project in user namespace' do
|
||||||
|
project = create_project(user, opts)
|
||||||
|
|
||||||
expect(project).to be_valid
|
expect(project).to be_valid
|
||||||
expect(project.first_owner).to eq(user)
|
expect(project.first_owner).to eq(user)
|
||||||
expect(project.team.maintainers).to include(user)
|
expect(project.team.maintainers).not_to include(user)
|
||||||
expect(project.namespace).to eq(user.namespace)
|
expect(project.team.owners).to contain_exactly(user)
|
||||||
expect(project.project_namespace).to be_in_sync_with_project(project)
|
expect(project.namespace).to eq(user.namespace)
|
||||||
|
expect(project.project_namespace).to be_in_sync_with_project(project)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when personal_project_owner_with_owner_access feature flag is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(personal_project_owner_with_owner_access: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a project in user namespace' do
|
||||||
|
project = create_project(user, opts)
|
||||||
|
|
||||||
|
expect(project).to be_valid
|
||||||
|
expect(project.first_owner).to eq(user)
|
||||||
|
expect(project.team.maintainers).to contain_exactly(user)
|
||||||
|
expect(project.team.owners).to contain_exactly(user)
|
||||||
|
expect(project.namespace).to eq(user.namespace)
|
||||||
|
expect(project.project_namespace).to be_in_sync_with_project(project)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -162,7 +182,7 @@ RSpec.describe Projects::CreateService, '#execute' do
|
||||||
expect(project).to be_persisted
|
expect(project).to be_persisted
|
||||||
expect(project.owner).to eq(user)
|
expect(project.owner).to eq(user)
|
||||||
expect(project.first_owner).to eq(user)
|
expect(project.first_owner).to eq(user)
|
||||||
expect(project.team.maintainers).to contain_exactly(user)
|
expect(project.team.owners).to contain_exactly(user)
|
||||||
expect(project.namespace).to eq(user.namespace)
|
expect(project.namespace).to eq(user.namespace)
|
||||||
expect(project.project_namespace).to be_in_sync_with_project(project)
|
expect(project.project_namespace).to be_in_sync_with_project(project)
|
||||||
end
|
end
|
||||||
|
|
|
@ -52,7 +52,7 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do
|
||||||
it 'is called' do
|
it 'is called' do
|
||||||
ProjectAuthorization.delete_all
|
ProjectAuthorization.delete_all
|
||||||
|
|
||||||
expect(callback).to receive(:call).with(project.id, Gitlab::Access::MAINTAINER).once
|
expect(callback).to receive(:call).with(project.id, Gitlab::Access::OWNER).once
|
||||||
|
|
||||||
service.execute
|
service.execute
|
||||||
end
|
end
|
||||||
|
@ -73,7 +73,7 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do
|
||||||
to_be_removed = [project_authorization.project_id]
|
to_be_removed = [project_authorization.project_id]
|
||||||
|
|
||||||
to_be_added = [
|
to_be_added = [
|
||||||
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
|
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
|
||||||
]
|
]
|
||||||
|
|
||||||
expect(service).to receive(:update_authorizations)
|
expect(service).to receive(:update_authorizations)
|
||||||
|
@ -83,14 +83,14 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes duplicate entries' do
|
it 'removes duplicate entries' do
|
||||||
[Gitlab::Access::MAINTAINER, Gitlab::Access::REPORTER].each do |access_level|
|
[Gitlab::Access::OWNER, Gitlab::Access::REPORTER].each do |access_level|
|
||||||
user.project_authorizations.create!(project: project, access_level: access_level)
|
user.project_authorizations.create!(project: project, access_level: access_level)
|
||||||
end
|
end
|
||||||
|
|
||||||
to_be_removed = [project.id]
|
to_be_removed = [project.id]
|
||||||
|
|
||||||
to_be_added = [
|
to_be_added = [
|
||||||
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
|
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
|
||||||
]
|
]
|
||||||
expect(service).to(
|
expect(service).to(
|
||||||
receive(:update_authorizations)
|
receive(:update_authorizations)
|
||||||
|
@ -103,7 +103,7 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do
|
||||||
project_authorization = ProjectAuthorization.where(
|
project_authorization = ProjectAuthorization.where(
|
||||||
project_id: project.id,
|
project_id: project.id,
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
access_level: Gitlab::Access::MAINTAINER)
|
access_level: Gitlab::Access::OWNER)
|
||||||
expect(project_authorization).to exist
|
expect(project_authorization).to exist
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do
|
||||||
to_be_removed = [project_authorization.project_id]
|
to_be_removed = [project_authorization.project_id]
|
||||||
|
|
||||||
to_be_added = [
|
to_be_added = [
|
||||||
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::MAINTAINER }
|
{ user_id: user.id, project_id: project.id, access_level: Gitlab::Access::OWNER }
|
||||||
]
|
]
|
||||||
|
|
||||||
expect(service).to receive(:update_authorizations)
|
expect(service).to receive(:update_authorizations)
|
||||||
|
|
|
@ -93,13 +93,12 @@ RSpec.shared_examples 'User views a wiki page' do
|
||||||
let(:path) { upload_file_to_wiki(wiki, user, 'dk.png') }
|
let(:path) { upload_file_to_wiki(wiki, user, 'dk.png') }
|
||||||
|
|
||||||
it do
|
it do
|
||||||
expect(page).to have_xpath("//img[@data-src='#{wiki.wiki_base_path}/#{path}']")
|
expect(page).to have_xpath("//img[@src='#{wiki.wiki_base_path}/#{path}']")
|
||||||
expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/#{path}")
|
expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/#{path}")
|
||||||
|
|
||||||
click_on('image')
|
click_on('image')
|
||||||
|
|
||||||
expect(current_path).to match("wikis/#{path}")
|
expect(current_path).to match("wikis/#{path}")
|
||||||
expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ RSpec.shared_examples 'User views wiki pages' do
|
||||||
before do
|
before do
|
||||||
page.within('.wiki-sort-dropdown') do
|
page.within('.wiki-sort-dropdown') do
|
||||||
click_button('Title')
|
click_button('Title')
|
||||||
click_link('Created date')
|
click_button('Created date')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -43,33 +43,4 @@ RSpec.describe 'shared/_global_alert.html.haml' do
|
||||||
expect(rendered).not_to have_selector('.gl-dismiss-btn')
|
expect(rendered).not_to have_selector('.gl-dismiss-btn')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'fixed layout' do
|
|
||||||
before do
|
|
||||||
allow(view).to receive(:fluid_layout).and_return(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'adds container classes' do
|
|
||||||
render
|
|
||||||
|
|
||||||
expect(rendered).to have_selector('.container-fluid.container-limited')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not add container classes if is_contained is true' do
|
|
||||||
render partial: 'shared/global_alert', locals: { is_contained: true }
|
|
||||||
|
|
||||||
expect(rendered).not_to have_selector('.container-fluid.container-limited')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'fluid layout' do
|
|
||||||
before do
|
|
||||||
allow(view).to receive(:fluid_layout).and_return(true)
|
|
||||||
render
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not add container classes' do
|
|
||||||
expect(rendered).not_to have_selector('.container-fluid.container-limited')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue