Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9223573b85
commit
7a33080fff
|
|
@ -3534,7 +3534,6 @@ Layout/LineLength:
|
|||
- 'spec/features/user_sorts_things_spec.rb'
|
||||
- 'spec/features/users/login_spec.rb'
|
||||
- 'spec/features/users/overview_spec.rb'
|
||||
- 'spec/features/users/signup_spec.rb'
|
||||
- 'spec/features/users/user_browses_projects_on_user_page_spec.rb'
|
||||
- 'spec/features/webauthn_spec.rb'
|
||||
- 'spec/finders/access_requests_finder_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
d3bb9a8fe1d9ff265edf3920c348b2d5993ca0a8
|
||||
fa4f0dbedd76758c0b0422da6e54441a5ac80d18
|
||||
|
|
|
|||
|
|
@ -95,11 +95,7 @@ export default class ProtectedBranchCreate {
|
|||
}
|
||||
|
||||
hasProtectedBranchSuccessAlert() {
|
||||
return (
|
||||
window.gon?.features?.branchRules &&
|
||||
this.isLocalStorageAvailable &&
|
||||
localStorage.getItem(IS_PROTECTED_BRANCH_CREATED)
|
||||
);
|
||||
return this.isLocalStorageAvailable && localStorage.getItem(IS_PROTECTED_BRANCH_CREATED);
|
||||
}
|
||||
|
||||
createSuccessAlert() {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export default {
|
|||
<component :is="tag">
|
||||
<hr v-if="separated" aria-hidden="true" class="gl-mx-4 gl-my-2" />
|
||||
<button
|
||||
class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-2 gl-py-2 gl-px-0 gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-appearance-none gl-border-0 gl-bg-transparent gl-text-left gl-w-full gl-focus--focus"
|
||||
class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-line-height-normal gl-mb-2 gl-py-3 gl-px-0 gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-appearance-none gl-border-0 gl-bg-transparent gl-text-left gl-w-full gl-focus--focus"
|
||||
:class="computedLinkClasses"
|
||||
data-qa-selector="menu_section_button"
|
||||
:data-qa-section-name="item.title"
|
||||
|
|
|
|||
|
|
@ -514,7 +514,6 @@ span.idiff {
|
|||
}
|
||||
|
||||
.blame-commit {
|
||||
padding: 5px 10px;
|
||||
width: 400px;
|
||||
flex: none;
|
||||
background: $gray-light;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
.web-ide-loader {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.web-ide-loader .tanuki-logo {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.web-ide-loader .tanuki,
|
||||
.web-ide-loader .right-cheek,
|
||||
.web-ide-loader .chin,
|
||||
.web-ide-loader .left-cheek {
|
||||
animation: animate-tanuki 1.5s infinite;
|
||||
}
|
||||
|
||||
.web-ide-loader .right-cheek {
|
||||
animation-delay: 0.35s;
|
||||
}
|
||||
|
||||
.web-ide-loader .chin {
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
|
||||
.web-ide-loader .left-cheek {
|
||||
animation-delay: 1.05s;
|
||||
}
|
||||
|
||||
@keyframes animate-tanuki {
|
||||
0%,
|
||||
50% {
|
||||
filter: brightness(1) grayscale(0);
|
||||
}
|
||||
|
||||
25% {
|
||||
filter: brightness(1.2) grayscale(0.2);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,7 @@ module Projects
|
|||
|
||||
feature_category :source_code_management
|
||||
|
||||
def index
|
||||
render_404 unless Feature.enabled?(:branch_rules, project)
|
||||
end
|
||||
def index; end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ module Projects
|
|||
urgency :low, [:show, :create_deploy_token]
|
||||
|
||||
def show
|
||||
push_frontend_feature_flag(:branch_rules, @project)
|
||||
render_show
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Users
|
||||
class SetNamespaceCommitEmail < BaseMutation
|
||||
graphql_name 'UserSetNamespaceCommitEmail'
|
||||
|
||||
argument :namespace_id,
|
||||
::Types::GlobalIDType[::Namespace],
|
||||
required: true,
|
||||
description: 'ID of the namespace to set the namespace commit email for.'
|
||||
|
||||
argument :email_id,
|
||||
::Types::GlobalIDType[::Email],
|
||||
required: false,
|
||||
description: 'ID of the email to set.'
|
||||
|
||||
field :namespace_commit_email,
|
||||
Types::Users::NamespaceCommitEmailType,
|
||||
null: true,
|
||||
description: 'User namespace commit email after mutation.'
|
||||
|
||||
authorize :read_namespace
|
||||
|
||||
def resolve(args)
|
||||
namespace = authorized_find!(args[:namespace_id])
|
||||
args[:email_id] = args[:email_id].model_id
|
||||
|
||||
result = ::Users::SetNamespaceCommitEmailService.new(current_user, namespace, args[:email_id], {}).execute
|
||||
{
|
||||
namespace_commit_email: result.payload[:namespace_commit_email],
|
||||
errors: result.errors
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_object(id)
|
||||
GitlabSchema.object_from_id(
|
||||
id, expected_type: [::Namespace, ::Namespaces::UserNamespace, ::Namespaces::ProjectNamespace]).sync
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -17,6 +17,10 @@ module Resolvers
|
|||
required: false,
|
||||
default_value: nil,
|
||||
description: 'Commit ref to get the blobs from. Default value is HEAD.'
|
||||
argument :ref_type, Types::RefTypeEnum,
|
||||
required: false,
|
||||
default_value: nil,
|
||||
description: 'Type of ref.'
|
||||
|
||||
# We fetch blobs from Gitaly efficiently but it still scales O(N) with the
|
||||
# number of paths being fetched, so apply a scaling limit to that.
|
||||
|
|
@ -24,7 +28,7 @@ module Resolvers
|
|||
super + (args[:paths] || []).size
|
||||
end
|
||||
|
||||
def resolve(paths:, ref:)
|
||||
def resolve(paths:, ref:, ref_type:)
|
||||
authorize!(repository.container)
|
||||
|
||||
return [] if repository.empty?
|
||||
|
|
@ -32,7 +36,13 @@ module Resolvers
|
|||
ref ||= repository.root_ref
|
||||
validate_ref(ref)
|
||||
|
||||
repository.blobs_at(paths.map { |path| [ref, path] })
|
||||
ref = ExtractsRef.qualify_ref(ref, ref_type)
|
||||
|
||||
repository.blobs_at(paths.map { |path| [ref, path] }).tap do |blobs|
|
||||
blobs.each do |blob|
|
||||
blob.ref_type = ref_type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ module Resolvers
|
|||
def resolve(**args)
|
||||
# Ensure merge commits can be returned by sending nil to Gitaly instead of '/'
|
||||
path = tree.path == '/' ? nil : tree.path
|
||||
commit = Gitlab::Git::Commit.last_for_path(tree.repository, tree.sha, path, literal_pathspec: true)
|
||||
commit = Gitlab::Git::Commit.last_for_path(tree.repository,
|
||||
ExtractsRef.qualify_ref(tree.sha, tree.ref_type), path, literal_pathspec: true)
|
||||
|
||||
::Commit.new(commit, tree.repository.project) if commit
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ module Resolvers
|
|||
argument :ref, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'Commit ref to get the tree for. Default value is HEAD.'
|
||||
argument :ref_type, Types::RefTypeEnum,
|
||||
required: false,
|
||||
description: 'Type of ref.'
|
||||
|
||||
alias_method :repository, :object
|
||||
|
||||
|
|
@ -25,7 +28,6 @@ module Resolvers
|
|||
return if repository.empty?
|
||||
|
||||
cursor = args.delete(:after)
|
||||
args[:ref] ||= :head
|
||||
|
||||
pagination_params = {
|
||||
limit: @field.max_page_size || 100,
|
||||
|
|
@ -33,9 +35,11 @@ module Resolvers
|
|||
}
|
||||
|
||||
tree = repository.tree(
|
||||
args[:ref], args[:path], recursive: args[:recursive],
|
||||
skip_flat_paths: false,
|
||||
pagination_params: pagination_params
|
||||
args[:ref].presence || :head,
|
||||
args[:path], recursive: args[:recursive],
|
||||
skip_flat_paths: false,
|
||||
pagination_params: pagination_params,
|
||||
ref_type: args[:ref_type]
|
||||
)
|
||||
|
||||
next_cursor = tree.cursor&.next_cursor
|
||||
|
|
|
|||
|
|
@ -17,14 +17,18 @@ module Resolvers
|
|||
argument :ref, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'Commit ref to get the tree for. Default value is HEAD.'
|
||||
argument :ref_type, Types::RefTypeEnum,
|
||||
required: false,
|
||||
description: 'Type of ref.'
|
||||
|
||||
alias_method :repository, :object
|
||||
|
||||
def resolve(**args)
|
||||
return unless repository.exists?
|
||||
|
||||
args[:ref] ||= :head
|
||||
repository.tree(args[:ref], args[:path], recursive: args[:recursive])
|
||||
ref = (args[:ref].presence || :head)
|
||||
|
||||
repository.tree(ref, args[:path], recursive: args[:recursive], ref_type: args[:ref_type])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ module Types
|
|||
mount_mutation Mutations::Pages::MarkOnboardingComplete
|
||||
mount_mutation Mutations::SavedReplies::Destroy
|
||||
mount_mutation Mutations::Uploads::Delete
|
||||
mount_mutation Mutations::Users::SetNamespaceCommitEmail
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
class RefTypeEnum < BaseEnum
|
||||
graphql_name 'RefType'
|
||||
description 'Type of ref'
|
||||
|
||||
value 'HEADS', description: 'Ref type for branches.', value: 'heads'
|
||||
value 'TAGS', description: 'Ref type for tags.', value: 'tags'
|
||||
end
|
||||
end
|
||||
|
|
@ -17,11 +17,13 @@ class Analytics::CycleAnalytics::Aggregation < ApplicationRecord
|
|||
end
|
||||
|
||||
def consistency_check_cursor_for(model)
|
||||
return {} if self["last_consistency_check_#{model.issuable_model.table_name}_issuable_id"].nil?
|
||||
|
||||
{
|
||||
:start_event_timestamp => self["last_consistency_check_#{model.issuable_model.table_name}_start_event_timestamp"],
|
||||
:end_event_timestamp => self["last_consistency_check_#{model.issuable_model.table_name}_end_event_timestamp"],
|
||||
model.issuable_id_column => self["last_consistency_check_#{model.issuable_model.table_name}_issuable_id"]
|
||||
}.compact
|
||||
}
|
||||
end
|
||||
|
||||
def refresh_last_run(mode)
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ class Blob < SimpleDelegator
|
|||
].freeze
|
||||
|
||||
attr_reader :container
|
||||
attr_accessor :ref_type
|
||||
|
||||
delegate :repository, to: :container, allow_nil: true
|
||||
delegate :project, to: :repository, allow_nil: true
|
||||
|
|
|
|||
|
|
@ -691,7 +691,7 @@ class Repository
|
|||
@head_tree ||= Tree.new(self, root_ref, nil, skip_flat_paths: skip_flat_paths)
|
||||
end
|
||||
|
||||
def tree(sha = :head, path = nil, recursive: false, skip_flat_paths: true, pagination_params: nil)
|
||||
def tree(sha = :head, path = nil, recursive: false, skip_flat_paths: true, pagination_params: nil, ref_type: nil)
|
||||
if sha == :head
|
||||
return if empty? || root_ref.nil?
|
||||
|
||||
|
|
@ -699,10 +699,11 @@ class Repository
|
|||
return head_tree(skip_flat_paths: skip_flat_paths)
|
||||
else
|
||||
sha = head_commit.sha
|
||||
ref_type = nil
|
||||
end
|
||||
end
|
||||
|
||||
Tree.new(self, sha, path, recursive: recursive, skip_flat_paths: skip_flat_paths, pagination_params: pagination_params)
|
||||
Tree.new(self, sha, path, recursive: recursive, skip_flat_paths: skip_flat_paths, pagination_params: pagination_params, ref_type: ref_type)
|
||||
end
|
||||
|
||||
def blob_at_branch(branch_name, path)
|
||||
|
|
|
|||
|
|
@ -3,17 +3,25 @@
|
|||
class Tree
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
attr_accessor :repository, :sha, :path, :entries, :cursor
|
||||
attr_accessor :repository, :sha, :path, :entries, :cursor, :ref_type
|
||||
|
||||
def initialize(repository, sha, path = '/', recursive: false, skip_flat_paths: true, pagination_params: nil)
|
||||
def initialize(
|
||||
repository, sha, path = '/', recursive: false, skip_flat_paths: true, pagination_params: nil,
|
||||
ref_type: nil)
|
||||
path = '/' if path.blank?
|
||||
|
||||
@repository = repository
|
||||
@sha = sha
|
||||
@path = path
|
||||
|
||||
@ref_type = ExtractsRef.ref_type(ref_type)
|
||||
git_repo = @repository.raw_repository
|
||||
@entries, @cursor = Gitlab::Git::Tree.where(git_repo, @sha, @path, recursive, skip_flat_paths, pagination_params)
|
||||
|
||||
ref = ExtractsRef.qualify_ref(@sha, ref_type)
|
||||
|
||||
@entries, @cursor = Gitlab::Git::Tree.where(git_repo, ref, @path, recursive, skip_flat_paths, pagination_params)
|
||||
@entries.each do |entry|
|
||||
entry.ref_type = self.ref_type
|
||||
end
|
||||
end
|
||||
|
||||
def readme_path
|
||||
|
|
|
|||
|
|
@ -2302,6 +2302,12 @@ class User < ApplicationRecord
|
|||
}
|
||||
end
|
||||
|
||||
def namespace_commit_email_for_namespace(namespace)
|
||||
return if namespace.nil?
|
||||
|
||||
namespace_commit_emails.find_by(namespace: namespace)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# override, from Devise::Validatable
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class UserPolicy < BasePolicy
|
|||
enable :read_user_groups
|
||||
enable :read_saved_replies
|
||||
enable :read_user_email_address
|
||||
enable :admin_user_email_address
|
||||
end
|
||||
|
||||
rule { default }.enable :read_user_profile
|
||||
|
|
|
|||
|
|
@ -56,23 +56,23 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
|
|||
end
|
||||
|
||||
def web_url
|
||||
url_helpers.project_blob_url(project, ref_qualified_path)
|
||||
url_helpers.project_blob_url(*path_params)
|
||||
end
|
||||
|
||||
def web_path
|
||||
url_helpers.project_blob_path(project, ref_qualified_path)
|
||||
url_helpers.project_blob_path(*path_params)
|
||||
end
|
||||
|
||||
def edit_blob_path
|
||||
url_helpers.project_edit_blob_path(project, ref_qualified_path)
|
||||
url_helpers.project_edit_blob_path(*path_params)
|
||||
end
|
||||
|
||||
def raw_path
|
||||
url_helpers.project_raw_path(project, ref_qualified_path)
|
||||
url_helpers.project_raw_path(*path_params)
|
||||
end
|
||||
|
||||
def replace_path
|
||||
url_helpers.project_update_blob_path(project, ref_qualified_path)
|
||||
url_helpers.project_update_blob_path(*path_params)
|
||||
end
|
||||
|
||||
def pipeline_editor_path
|
||||
|
|
@ -164,6 +164,18 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
|
|||
|
||||
private
|
||||
|
||||
def path_params
|
||||
if ref_type.present?
|
||||
[project, ref_qualified_path, { ref_type: ref_type }]
|
||||
else
|
||||
[project, ref_qualified_path]
|
||||
end
|
||||
end
|
||||
|
||||
def ref_type
|
||||
blob.ref_type
|
||||
end
|
||||
|
||||
def url_helpers
|
||||
Gitlab::Routing.url_helpers
|
||||
end
|
||||
|
|
@ -179,7 +191,12 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
|
|||
end
|
||||
|
||||
def ref_qualified_path
|
||||
File.join(blob.commit_id, blob.path)
|
||||
# If `ref_type` is present the commit_id will include the ref qualifier e.g. `refs/heads/`.
|
||||
# We only accept/return unqualified refs so we need to remove the qualifier from the `commit_id`.
|
||||
|
||||
commit_id = ExtractsRef.unqualify_ref(blob.commit_id, ref_type)
|
||||
|
||||
File.join(commit_id, blob.path)
|
||||
end
|
||||
|
||||
def load_all_blob_data
|
||||
|
|
|
|||
|
|
@ -4,10 +4,23 @@ class TreeEntryPresenter < Gitlab::View::Presenter::Delegated
|
|||
presents nil, as: :tree
|
||||
|
||||
def web_url
|
||||
Gitlab::Routing.url_helpers.project_tree_url(tree.repository.project, File.join(tree.commit_id, tree.path))
|
||||
Gitlab::Routing.url_helpers.project_tree_url(tree.repository.project, ref_qualified_path,
|
||||
ref_type: tree.ref_type)
|
||||
end
|
||||
|
||||
def web_path
|
||||
Gitlab::Routing.url_helpers.project_tree_path(tree.repository.project, File.join(tree.commit_id, tree.path))
|
||||
Gitlab::Routing.url_helpers.project_tree_path(tree.repository.project, ref_qualified_path,
|
||||
ref_type: tree.ref_type)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ref_qualified_path
|
||||
# If `ref_type` is present the commit_id will include the ref qualifier e.g. `refs/heads/`.
|
||||
# We only accept/return unqualified refs so we need to remove the qualifier from the `commit_id`.
|
||||
|
||||
commit_id = ExtractsRef.unqualify_ref(tree.commit_id, ref_type)
|
||||
|
||||
File.join(commit_id, tree.path)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class SetNamespaceCommitEmailService
|
||||
include Gitlab::Allowable
|
||||
|
||||
attr_reader :current_user, :target_user, :namespace, :email_id
|
||||
|
||||
def initialize(current_user, namespace, email_id, params)
|
||||
@current_user = current_user
|
||||
@target_user = params.delete(:user) || current_user
|
||||
@namespace = namespace
|
||||
@email_id = email_id
|
||||
end
|
||||
|
||||
def execute
|
||||
return error(_('Namespace must be provided.')) if namespace.nil?
|
||||
|
||||
unless can?(current_user, :admin_user_email_address, target_user)
|
||||
return error(_("User doesn't exist or you don't have permission to change namespace commit emails."))
|
||||
end
|
||||
|
||||
unless can?(target_user, :read_namespace, namespace)
|
||||
return error(_("Namespace doesn't exist or you don't have permission."))
|
||||
end
|
||||
|
||||
email = target_user.emails.find_by(id: email_id) unless email_id.nil? # rubocop: disable CodeReuse/ActiveRecord
|
||||
existing_namespace_commit_email = target_user.namespace_commit_email_for_namespace(namespace)
|
||||
if existing_namespace_commit_email.nil?
|
||||
return error(_('Email must be provided.')) if email.nil?
|
||||
|
||||
create_namespace_commit_email(email)
|
||||
elsif email_id.nil?
|
||||
remove_namespace_commit_email(existing_namespace_commit_email)
|
||||
else
|
||||
update_namespace_commit_email(existing_namespace_commit_email, email)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remove_namespace_commit_email(namespace_commit_email)
|
||||
namespace_commit_email.destroy
|
||||
success(nil)
|
||||
end
|
||||
|
||||
def create_namespace_commit_email(email)
|
||||
namespace_commit_email = ::Users::NamespaceCommitEmail.new(
|
||||
user: target_user,
|
||||
namespace: namespace,
|
||||
email: email
|
||||
)
|
||||
|
||||
save_namespace_commit_email(namespace_commit_email)
|
||||
end
|
||||
|
||||
def update_namespace_commit_email(namespace_commit_email, email)
|
||||
namespace_commit_email.email = email
|
||||
|
||||
save_namespace_commit_email(namespace_commit_email)
|
||||
end
|
||||
|
||||
def save_namespace_commit_email(namespace_commit_email)
|
||||
if !namespace_commit_email.save
|
||||
error_in_save(namespace_commit_email)
|
||||
else
|
||||
success(namespace_commit_email)
|
||||
end
|
||||
end
|
||||
|
||||
def success(namespace_commit_email)
|
||||
ServiceResponse.success(payload: {
|
||||
namespace_commit_email: namespace_commit_email
|
||||
})
|
||||
end
|
||||
|
||||
def error(message)
|
||||
ServiceResponse.error(message: message)
|
||||
end
|
||||
|
||||
def error_in_save(namespace_commit_email)
|
||||
return error(_('Failed to save namespace commit email.')) if namespace_commit_email.errors.empty?
|
||||
|
||||
error(namespace_commit_email.errors.full_messages.to_sentence)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -75,4 +75,5 @@
|
|||
.gl-pt-5
|
||||
= markdown_field(Gitlab::CurrentSettings.current_application_settings, :sign_in_text)
|
||||
= render 'devise/shared/terms_of_service_notice', button_text: button_text
|
||||
= yield :omniauth_providers_bottom if show_omniauth_providers
|
||||
|
||||
= yield :omniauth_providers_bottom if show_omniauth_providers
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
= _("Register with:")
|
||||
.gl-text-center.gl-ml-auto.gl-mr-auto
|
||||
- providers.each do |provider|
|
||||
= link_to omniauth_authorize_path(:user, provider, register_omniauth_params(local_assigns)), method: :post, class: "btn gl-button btn-default gl-w-full gl-mb-4 js-oauth-login #{qa_selector_for_provider(provider)}", data: { provider: provider }, id: "oauth-login-#{provider}" do
|
||||
= button_to omniauth_authorize_path(:user, provider, register_omniauth_params(local_assigns)), class: "btn gl-button btn-default gl-w-full gl-mb-4 js-oauth-login #{qa_selector_for_provider(provider)}", data: { provider: provider, track_action: "#{provider}_sso", track_label: tracking_label }, id: "oauth-login-#{provider}" do
|
||||
- if provider_has_icon?(provider)
|
||||
= provider_image_tag(provider)
|
||||
%span.gl-button-text
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
= _("Create an account using:")
|
||||
.gl-display-flex.gl-justify-content-between.gl-flex-wrap
|
||||
- providers.each do |provider|
|
||||
= link_to omniauth_authorize_path(:user, provider, register_omniauth_params(local_assigns)), method: :post, class: "btn gl-button btn-default gl-w-full gl-mb-4 js-oauth-login #{qa_selector_for_provider(provider)}", data: { provider: provider }, id: "oauth-login-#{provider}" do
|
||||
= button_to omniauth_authorize_path(:user, provider, register_omniauth_params(local_assigns)), class: "btn gl-button btn-default gl-w-full gl-mb-4 js-oauth-login #{qa_selector_for_provider(provider)}", data: { provider: provider, track_action: "#{provider}_sso", track_label: tracking_label }, id: "oauth-login-#{provider}" do
|
||||
- if provider_has_icon?(provider)
|
||||
= provider_image_tag(provider)
|
||||
%span.gl-button-text
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
- if Feature.disabled?(:restyle_login_page, @project)
|
||||
.omniauth-divider.gl-display-flex.gl-align-items-center
|
||||
= _("or")
|
||||
= render 'devise/shared/signup_omniauth_provider_list', providers: enabled_button_based_providers
|
||||
= render 'devise/shared/signup_omniauth_provider_list', providers: enabled_button_based_providers, tracking_label: "free_registration"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
- page_title _("IDE"), @project.full_name
|
||||
- add_page_specific_style 'page_bundles/web_ide_loader'
|
||||
|
||||
- unless use_new_web_ide?
|
||||
- add_page_specific_style 'page_bundles/build'
|
||||
|
|
@ -9,4 +10,4 @@
|
|||
|
||||
- data = ide_data(project: @project, fork_info: @fork_info, params: params)
|
||||
|
||||
= render partial: 'shared/ide_root', locals: { data: data, loading_text: _('Loading the GitLab IDE...') }
|
||||
= render partial: 'shared/ide_root', locals: { data: data, loading_text: _('Loading the GitLab IDE') }
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
- line_count = blame_group[:lines].count
|
||||
|
||||
.tr{ class: ('last-row' if groups_length == index) }
|
||||
.td.blame-commit.commit{ class: commit_data.age_map_class }
|
||||
.td.blame-commit.commit.gl-py-3.gl-px-4{ class: commit_data.age_map_class }
|
||||
= commit_data.author_avatar
|
||||
|
||||
.commit-row-title
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- add_page_specific_style 'page_bundles/branches'
|
||||
- page_title _('Branches')
|
||||
- add_to_breadcrumbs(_('Repository'), project_tree_path(@project))
|
||||
- is_branch_rules_available = (can? current_user, :maintainer_access, @project) && Feature.enabled?(:branch_rules, @project)
|
||||
- can_access_branch_rules = can?(current_user, :maintainer_access, @project)
|
||||
- can_push_code = (can? current_user, :push_code, @project)
|
||||
|
||||
-# Possible values for variables passed down from the projects/branches_controller.rb
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
sorted_by: @sort }
|
||||
}
|
||||
|
||||
- if is_branch_rules_available
|
||||
- if can_access_branch_rules
|
||||
= link_to project_settings_repository_path(@project, anchor: 'js-branch-rules'), class: 'gl-button btn btn-default' do
|
||||
= s_('Branches|View branch rules')
|
||||
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
= render_if_exists 'projects/commits/mirror_status'
|
||||
|
||||
- if is_branch_rules_available
|
||||
- if can_access_branch_rules
|
||||
= render 'branch_rules_info'
|
||||
|
||||
.js-branch-list{ data: { diverging_counts_endpoint: diverging_commit_counts_namespace_project_branches_path(@project.namespace, @project, format: :json), default_branch: @project.default_branch } }
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@
|
|||
- @force_desktop_expanded_sidebar = true
|
||||
|
||||
= render "projects/branch_defaults/show"
|
||||
- if Feature.enabled?(:branch_rules, @project)
|
||||
= render "projects/branch_rules/show"
|
||||
= render "projects/branch_rules/show"
|
||||
= render_if_exists "projects/push_rules/index"
|
||||
= render "projects/mirrors/mirror_repos"
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
|
||||
-# Fix for iOS 13+, the height of the page is actually less than
|
||||
-# 100vh because of the presence of the bottom bar
|
||||
- @body_class = 'gl-max-h-full gl-fixed'
|
||||
|
||||
#ide.gl--flex-center.gl-h-full{ data: data }
|
||||
.gl-text-center
|
||||
= gl_loading_icon(size: 'md')
|
||||
%h2.clgray= loading_text
|
||||
#ide.gl-h-full{ data: data }
|
||||
.web-ide-loader.gl-display-flex.gl-justify-content-center.gl-align-items-center.gl-flex-direction-column.gl-h-full.gl-mr-auto.gl-ml-auto
|
||||
= brand_header_logo
|
||||
%h3.clblack.gl-mt-6= loading_text
|
||||
|
|
|
|||
|
|
@ -336,6 +336,7 @@ module Gitlab
|
|||
config.assets.precompile << "page_bundles/todos.css"
|
||||
config.assets.precompile << "page_bundles/tree.css"
|
||||
config.assets.precompile << "page_bundles/users.css"
|
||||
config.assets.precompile << "page_bundles/web_ide_loader.css"
|
||||
config.assets.precompile << "page_bundles/wiki.css"
|
||||
config.assets.precompile << "page_bundles/work_items.css"
|
||||
config.assets.precompile << "page_bundles/xterm.css"
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: branch_rules
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88279
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363170
|
||||
milestone: '15.1'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: true
|
||||
|
|
@ -5,4 +5,4 @@ rollout_issue_url:
|
|||
milestone: '16.1'
|
||||
type: development
|
||||
group: group::code review
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: compare_project_authorization_linear_cte
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122886
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414310
|
||||
milestone: '16.1'
|
||||
type: development
|
||||
group: group::authentication and authorization
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
key_path: redis_hll_counters.quickactions.i_quickactions_unlink_monthly
|
||||
description: Count of MAU using the `/unlink` quick action
|
||||
product_section: dev
|
||||
product_stage: plan
|
||||
product_group: product_planning
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "16.1"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123485
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_quickactions_unlink
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
---
|
||||
key_path: redis_hll_counters.quickactions.i_quickactions_unlink_weekly
|
||||
name: quickactions_unlink_weekly
|
||||
description: Count of WAU using the `/unlink` quick action
|
||||
product_section: dev
|
||||
product_stage: plan
|
||||
|
|
@ -16,7 +15,6 @@ instrumentation_class: RedisHLLMetric
|
|||
options:
|
||||
events:
|
||||
- i_quickactions_unlink
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexOnPackagesIdIdToPackageBuildInfos < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_packages_build_infos_package_id_id'
|
||||
|
||||
def up
|
||||
add_concurrent_index :packages_build_infos, [:package_id, :id], name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :packages_build_infos, name: INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
5fadce4dbc2280ca1d68f8271f4d44ea3c492769b65ebb1d8f2ae94cfb6d6c75
|
||||
|
|
@ -32019,6 +32019,8 @@ CREATE INDEX index_p_ci_runner_machine_builds_on_runner_machine_id ON ONLY p_ci_
|
|||
|
||||
CREATE INDEX index_packages_build_infos_on_pipeline_id ON packages_build_infos USING btree (pipeline_id);
|
||||
|
||||
CREATE INDEX index_packages_build_infos_package_id_id ON packages_build_infos USING btree (package_id, id);
|
||||
|
||||
CREATE INDEX index_packages_build_infos_package_id_pipeline_id_id ON packages_build_infos USING btree (package_id, pipeline_id, id);
|
||||
|
||||
CREATE UNIQUE INDEX index_packages_composer_cache_namespace_and_sha ON packages_composer_cache_files USING btree (namespace_id, file_sha256);
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ Streaming destinations receive **all** audit event data, which could include sen
|
|||
|
||||
Users with the Owner role for a group can add streaming destinations for it:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Security and Compliance > Audit events**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Secure > Audit events**.
|
||||
1. On the main area, select **Streams** tab.
|
||||
1. Select **Add streaming destination** to show the section for adding destinations.
|
||||
1. Enter the destination URL to add.
|
||||
|
|
@ -70,7 +70,8 @@ Prerequisites:
|
|||
|
||||
To add a streaming destination for an instance:
|
||||
|
||||
1. On the top bar, select **Main menu > Admin**.
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. On the left sidebar, select **Monitoring > Audit events**.
|
||||
1. On the main area, select **Streams** tab.
|
||||
1. Select **Add streaming destination** to show the section for adding destinations.
|
||||
|
|
@ -180,8 +181,8 @@ Users with the Owner role for a group can list streaming destinations.
|
|||
|
||||
To list the streaming destinations for a top-level group:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Security and Compliance > Audit events**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Secure > Audit events**.
|
||||
1. On the main area, select **Streams** tab.
|
||||
1. To the right of the item, select **Edit** (**{pencil}**) to see all the custom HTTP headers.
|
||||
|
||||
|
|
@ -199,7 +200,8 @@ Prerequisites:
|
|||
|
||||
To list the streaming destinations for an instance:
|
||||
|
||||
1. On the top bar, select **Main menu > Admin**.
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. On the left sidebar, select **Monitoring > Audit events**.
|
||||
1. On the main area, select **Streams** tab.
|
||||
|
||||
|
|
@ -276,8 +278,8 @@ Users with the Owner role for a group and instance administrators can update str
|
|||
|
||||
To update a streaming destinations custom HTTP headers:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Security and Compliance > Audit events**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Secure > Audit events**.
|
||||
1. On the main area, select **Streams** tab.
|
||||
1. To the right of the item, select **Edit** (**{pencil}**).
|
||||
1. Locate the **Custom HTTP headers** table.
|
||||
|
|
@ -368,15 +370,15 @@ When the last destination is successfully deleted, streaming is disabled for the
|
|||
|
||||
To delete a streaming destination:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Security and Compliance > Audit events**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Secure > Audit events**.
|
||||
1. On the main area, select the **Streams** tab.
|
||||
1. To the right of the item, select **Delete** (**{remove}**).
|
||||
|
||||
To delete only the custom HTTP headers for a streaming destination:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Security and Compliance > Audit events**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Secure > Audit events**.
|
||||
1. On the main area, select the **Streams** tab.
|
||||
1. To the right of the item, **Edit** (**{pencil}**).
|
||||
1. Locate the **Custom HTTP headers** table.
|
||||
|
|
@ -398,7 +400,8 @@ Prerequisites:
|
|||
|
||||
To delete the streaming destinations for an instance:
|
||||
|
||||
1. On the top bar, select **Main menu > Admin**.
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. On the left sidebar, select **Monitoring > Audit events**.
|
||||
1. On the main area, select the **Streams** tab.
|
||||
1. To the right of the item, select **Delete** (**{remove}**).
|
||||
|
|
@ -484,8 +487,8 @@ the destination's value when [listing streaming destinations](#list-streaming-de
|
|||
|
||||
Users with the Owner role for a group can list streaming destinations and see the verification tokens:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Security and Compliance > Audit events**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Secure > Audit events**.
|
||||
1. On the main area, select the **Streams**.
|
||||
1. View the verification token on the right side of each item.
|
||||
|
||||
|
|
@ -503,7 +506,8 @@ Prerequisites:
|
|||
|
||||
To list streaming destinations for an instance and see the verification tokens:
|
||||
|
||||
1. On the top bar, select **Main menu > Admin**.
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. On the left sidebar, select **Monitoring > Audit events**.
|
||||
1. On the main area, select the **Streams**.
|
||||
1. View the verification token on the right side of each item.
|
||||
|
|
|
|||
|
|
@ -781,7 +781,7 @@ Plan.default.actual_limits.update!(dast_profile_schedules: 50)
|
|||
|
||||
### Maximum size and depth of CI/CD configuration YAML files
|
||||
|
||||
The default maximum size of a CI/CD configuration YAML file is 1 megabyte and the
|
||||
The default maximum size of a single CI/CD configuration YAML file is 1 megabyte and the
|
||||
default depth is 100.
|
||||
|
||||
You can change these limits in the [GitLab Rails console](operations/rails_console.md#starting-a-rails-console-session):
|
||||
|
|
|
|||
|
|
@ -6817,6 +6817,26 @@ Input type: `UserPreferencesUpdateInput`
|
|||
| <a id="mutationuserpreferencesupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationuserpreferencesupdateuserpreferences"></a>`userPreferences` | [`UserPreferences`](#userpreferences) | User preferences after mutation. |
|
||||
|
||||
### `Mutation.userSetNamespaceCommitEmail`
|
||||
|
||||
Input type: `UserSetNamespaceCommitEmailInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationusersetnamespacecommitemailclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationusersetnamespacecommitemailemailid"></a>`emailId` | [`EmailID`](#emailid) | ID of the email to set. |
|
||||
| <a id="mutationusersetnamespacecommitemailnamespaceid"></a>`namespaceId` | [`NamespaceID!`](#namespaceid) | ID of the namespace to set the namespace commit email for. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationusersetnamespacecommitemailclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationusersetnamespacecommitemailerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationusersetnamespacecommitemailnamespacecommitemail"></a>`namespaceCommitEmail` | [`NamespaceCommitEmail`](#namespacecommitemail) | User namespace commit email after mutation. |
|
||||
|
||||
### `Mutation.vulnerabilityConfirm`
|
||||
|
||||
Input type: `VulnerabilityConfirmInput`
|
||||
|
|
@ -7548,6 +7568,29 @@ The edge type for [`AuditEventStreamingHeader`](#auditeventstreamingheader).
|
|||
| <a id="auditeventstreamingheaderedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="auditeventstreamingheaderedgenode"></a>`node` | [`AuditEventStreamingHeader`](#auditeventstreamingheader) | The item at the end of the edge. |
|
||||
|
||||
#### `AuditEventsStreamingInstanceHeaderConnection`
|
||||
|
||||
The connection type for [`AuditEventsStreamingInstanceHeader`](#auditeventsstreaminginstanceheader).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="auditeventsstreaminginstanceheaderconnectionedges"></a>`edges` | [`[AuditEventsStreamingInstanceHeaderEdge]`](#auditeventsstreaminginstanceheaderedge) | A list of edges. |
|
||||
| <a id="auditeventsstreaminginstanceheaderconnectionnodes"></a>`nodes` | [`[AuditEventsStreamingInstanceHeader]`](#auditeventsstreaminginstanceheader) | A list of nodes. |
|
||||
| <a id="auditeventsstreaminginstanceheaderconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `AuditEventsStreamingInstanceHeaderEdge`
|
||||
|
||||
The edge type for [`AuditEventsStreamingInstanceHeader`](#auditeventsstreaminginstanceheader).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="auditeventsstreaminginstanceheaderedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="auditeventsstreaminginstanceheaderedgenode"></a>`node` | [`AuditEventsStreamingInstanceHeader`](#auditeventsstreaminginstanceheader) | The item at the end of the edge. |
|
||||
|
||||
#### `AwardEmojiConnection`
|
||||
|
||||
The connection type for [`AwardEmoji`](#awardemoji).
|
||||
|
|
@ -16838,6 +16881,7 @@ Represents an external resource to send instance audit events to.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="instanceexternalauditeventdestinationdestinationurl"></a>`destinationUrl` | [`String!`](#string) | External destination to send audit events to. |
|
||||
| <a id="instanceexternalauditeventdestinationheaders"></a>`headers` | [`AuditEventsStreamingInstanceHeaderConnection!`](#auditeventsstreaminginstanceheaderconnection) | List of additional HTTP headers sent with each event. (see [Connections](#connections)) |
|
||||
| <a id="instanceexternalauditeventdestinationid"></a>`id` | [`ID!`](#id) | ID of the destination. |
|
||||
| <a id="instanceexternalauditeventdestinationverificationtoken"></a>`verificationToken` | [`String!`](#string) | Verification token to validate source of event. |
|
||||
|
||||
|
|
@ -21712,6 +21756,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="repositoryblobspaths"></a>`paths` | [`[String!]!`](#string) | Array of desired blob paths. |
|
||||
| <a id="repositoryblobsref"></a>`ref` | [`String`](#string) | Commit ref to get the blobs from. Default value is HEAD. |
|
||||
| <a id="repositoryblobsreftype"></a>`refType` | [`RefType`](#reftype) | Type of ref. |
|
||||
|
||||
##### `Repository.branchNames`
|
||||
|
||||
|
|
@ -21756,6 +21801,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="repositorypaginatedtreepath"></a>`path` | [`String`](#string) | Path to get the tree for. Default value is the root of the repository. |
|
||||
| <a id="repositorypaginatedtreerecursive"></a>`recursive` | [`Boolean`](#boolean) | Used to get a recursive tree. Default is false. |
|
||||
| <a id="repositorypaginatedtreeref"></a>`ref` | [`String`](#string) | Commit ref to get the tree for. Default value is HEAD. |
|
||||
| <a id="repositorypaginatedtreereftype"></a>`refType` | [`RefType`](#reftype) | Type of ref. |
|
||||
|
||||
##### `Repository.tree`
|
||||
|
||||
|
|
@ -21770,6 +21816,7 @@ Returns [`Tree`](#tree).
|
|||
| <a id="repositorytreepath"></a>`path` | [`String`](#string) | Path to get the tree for. Default value is the root of the repository. |
|
||||
| <a id="repositorytreerecursive"></a>`recursive` | [`Boolean`](#boolean) | Used to get a recursive tree. Default is false. |
|
||||
| <a id="repositorytreeref"></a>`ref` | [`String`](#string) | Commit ref to get the tree for. Default value is HEAD. |
|
||||
| <a id="repositorytreereftype"></a>`refType` | [`RefType`](#reftype) | Type of ref. |
|
||||
|
||||
### `RepositoryBlob`
|
||||
|
||||
|
|
@ -26023,6 +26070,15 @@ Project member relation.
|
|||
| <a id="projectmemberrelationinvited_groups"></a>`INVITED_GROUPS` | Invited Groups members. |
|
||||
| <a id="projectmemberrelationshared_into_ancestors"></a>`SHARED_INTO_ANCESTORS` | Shared Into Ancestors members. |
|
||||
|
||||
### `RefType`
|
||||
|
||||
Type of ref.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="reftypeheads"></a>`HEADS` | Ref type for branches. |
|
||||
| <a id="reftypetags"></a>`TAGS` | Ref type for tags. |
|
||||
|
||||
### `RegistryState`
|
||||
|
||||
State of a Geo registry.
|
||||
|
|
@ -27026,6 +27082,12 @@ Duration between two instants, represented as a fractional number of seconds.
|
|||
|
||||
For example: 12.3334.
|
||||
|
||||
### `EmailID`
|
||||
|
||||
A `EmailID` is a global ID. It is encoded as a string.
|
||||
|
||||
An example `EmailID` is: `"gid://gitlab/Email/1"`.
|
||||
|
||||
### `EnvironmentID`
|
||||
|
||||
A `EnvironmentID` is a global ID. It is encoded as a string.
|
||||
|
|
|
|||
|
|
@ -330,6 +330,74 @@ Example response:
|
|||
|
||||
By default, the `GET` request returns 20 results, because the API is [paginated](rest/index.md#pagination).
|
||||
|
||||
## List package pipelines
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/341950) in GitLab 16.1.
|
||||
|
||||
Get a list of pipelines for a single package. The results are sorted by `id` in descending order.
|
||||
|
||||
The results are [paginated](rest/index.md#keyset-based-pagination) and return up to 20 records per page.
|
||||
|
||||
```plaintext
|
||||
GET /projects/:id/packages/:package_id/pipelines
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) |
|
||||
| `package_id` | integer | yes | ID of a package. |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/packages/:package_id/pipelines"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"iid": 1,
|
||||
"project_id": 9,
|
||||
"sha": "2b6127f6bb6f475c4e81afcc2251e3f941e554f9",
|
||||
"ref": "mytag",
|
||||
"status": "failed",
|
||||
"source": "push",
|
||||
"created_at": "2023-02-01T12:19:21.895Z",
|
||||
"updated_at": "2023-02-01T14:00:05.922Z",
|
||||
"web_url": "http://gdk.test:3001/feature-testing/composer-repository/-/pipelines/1",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "root",
|
||||
"name": "Administrator",
|
||||
"state": "active",
|
||||
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
|
||||
"web_url": "http://gdk.test:3001/root"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"iid": 2,
|
||||
"project_id": 9,
|
||||
"sha": "e564015ac6cb3d8617647802c875b27d392f72a6",
|
||||
"ref": "master",
|
||||
"status": "canceled",
|
||||
"source": "push",
|
||||
"created_at": "2023-02-01T12:23:23.694Z",
|
||||
"updated_at": "2023-02-01T12:26:28.635Z",
|
||||
"web_url": "http://gdk.test:3001/feature-testing/composer-repository/-/pipelines/2",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "root",
|
||||
"name": "Administrator",
|
||||
"state": "active",
|
||||
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
|
||||
"web_url": "http://gdk.test:3001/root"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Delete a project package
|
||||
|
||||
Deletes a project package.
|
||||
|
|
|
|||
|
|
@ -491,6 +491,7 @@ options:
|
|||
| [Group audit events](../audit_events.md#retrieve-all-group-audit-events) | `order_by=id`, `sort=desc` only | Authenticated users only. |
|
||||
| [Groups](../groups.md#list-groups) | `order_by=name`, `sort=asc` only | Unauthenticated users only. |
|
||||
| [Instance audit events](../audit_events.md#retrieve-all-instance-audit-events) | `order_by=id`, `sort=desc` only | Authenticated users only. |
|
||||
| [Package pipelines](../packages.md#list-package-pipelines) | `order_by=id`, `sort=desc` only | Authenticated users only. |
|
||||
| [Project jobs](../jobs.md#list-project-jobs) | `order_by=id`, `sort=desc` only | Authenticated users only. |
|
||||
| [Project audit events](../audit_events.md#retrieve-all-project-audit-events) | `order_by=id`, `sort=desc` only | Authenticated users only. |
|
||||
| [Projects](../projects.md) | `order_by=id` only | Authenticated and unauthenticated users. |
|
||||
|
|
|
|||
|
|
@ -61,8 +61,7 @@ of the changes.
|
|||
|
||||
## Continuous Deployment
|
||||
|
||||
[Continuous Deployment](https://www.airpair.com/continuous-deployment/posts/continuous-deployment-for-practical-people)
|
||||
is another step beyond Continuous Integration, similar to
|
||||
Continuous Deployment is another step beyond Continuous Integration, similar to
|
||||
Continuous Delivery. The difference is that instead of deploying your
|
||||
application manually, you set it to be deployed automatically.
|
||||
Human intervention is not required.
|
||||
|
|
|
|||
|
|
@ -34,17 +34,18 @@ your cluster's level.
|
|||
|
||||
**Project-level clusters:**
|
||||
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Infrastructure > Kubernetes clusters**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Operate > Kubernetes clusters**.
|
||||
|
||||
**Group-level clusters:**
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Kubernetes**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Operate > Kubernetes clusters**.
|
||||
|
||||
**Instance-level clusters:**
|
||||
|
||||
1. On the top bar, select **Main menu > Admin**.
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. On the left sidebar, select **Kubernetes**.
|
||||
|
||||
## Security implications for clusters connected with certificates
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ Start by [importing the example project by URL](../../../project/import/repo_by_
|
|||
|
||||
To import the project:
|
||||
|
||||
1. In GitLab, on the top bar, select **Main menu > Projects > View all projects**.
|
||||
1. In GitLab, on the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **View all your projects**..
|
||||
1. On the right of the page, select **New project**.
|
||||
1. Select **Import project**.
|
||||
1. Select **Repository by URL**.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ Start by [importing the example project by URL](../../../project/import/repo_by_
|
|||
|
||||
To import the project:
|
||||
|
||||
1. In GitLab, on the top bar, select **Main menu > Projects > View all projects**.
|
||||
1. In GitLab, on the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **View all your projects**.
|
||||
1. On the right of the page, select **New project**.
|
||||
1. Select **Import project**.
|
||||
1. Select **Repository by URL**.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ Start by [importing the example project by URL](../../../project/import/repo_by_
|
|||
|
||||
To import the project:
|
||||
|
||||
1. In GitLab, on the top bar, select **Main menu > Projects > View all projects**.
|
||||
1. In GitLab, on the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **View all your projects**.
|
||||
1. On the right of the page, select **New project**.
|
||||
1. Select **Import project**.
|
||||
1. Select **Repository by URL**.
|
||||
|
|
|
|||
|
|
@ -64,8 +64,8 @@ In each GitLab major release (for example, 15.0), the latest templates replace t
|
|||
|
||||
To use a Terraform template:
|
||||
|
||||
1. On the top bar, select **Main menu > Projects** and find the project you want to integrate with Terraform.
|
||||
1. On the left sidebar, select **Repository > Files**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project you want to integrate with Terraform.
|
||||
1. Select **Code > Repository**.
|
||||
1. Edit your `.gitlab-ci.yml` file, use the `include` attribute to fetch the Terraform template:
|
||||
|
||||
```yaml
|
||||
|
|
@ -73,7 +73,7 @@ To use a Terraform template:
|
|||
# To fetch the latest template, use:
|
||||
- template: Terraform.latest.gitlab-ci.yml
|
||||
# To fetch the advanced latest template, use:
|
||||
- template: Terraform/Base.latest.gitlab-ci.yml
|
||||
- template: Terraform/Base.latest.gitlab-ci.yml
|
||||
# To fetch the stable template, use:
|
||||
- template: Terraform.gitlab-ci.yml
|
||||
# To fetch the advanced stable template, use:
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ inconsistent. Instead, use a remote storage resource.
|
|||
[initialized for CI/CD](#initialize-a-terraform-state-as-a-backend-by-using-gitlab-cicd).
|
||||
1. Copy a pre-populated Terraform `init` command:
|
||||
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Infrastructure > Terraform states**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Operate > Terraform states**.
|
||||
1. Next to the environment you want to use, select **Actions**
|
||||
(**{ellipsis_v}**) and select **Copy Terraform init command**.
|
||||
|
||||
|
|
@ -294,8 +294,8 @@ To read the Terraform state in the target project, you need at least the Develop
|
|||
|
||||
To view Terraform state files:
|
||||
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Infrastructure > Terraform states**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Operate > Terraform states**.
|
||||
|
||||
[An epic exists](https://gitlab.com/groups/gitlab-org/-/epics/4563) to track improvements to this UI.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
module API
|
||||
class ProjectPackages < ::API::Base
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include PaginationParams
|
||||
|
||||
PIPELINE_COLUMNS = %i[id iid project_id sha ref status source created_at updated_at user_id].freeze
|
||||
|
||||
before do
|
||||
authorize_packages_access!(user_project)
|
||||
end
|
||||
|
|
@ -12,6 +15,13 @@ module API
|
|||
urgency :low
|
||||
|
||||
helpers ::API::Helpers::PackagesHelpers
|
||||
helpers do
|
||||
def package
|
||||
strong_memoize(:package) do # rubocop:disable Gitlab/StrongMemoizeAttr
|
||||
::Packages::PackageFinder.new(user_project, declared_params[:package_id]).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
params do
|
||||
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
|
||||
|
|
@ -66,14 +76,45 @@ module API
|
|||
end
|
||||
route_setting :authentication, job_token_allowed: true
|
||||
get ':id/packages/:package_id' do
|
||||
package = ::Packages::PackageFinder
|
||||
.new(user_project, params[:package_id]).execute
|
||||
|
||||
render_api_error!('Package not found', 404) unless package.default?
|
||||
|
||||
present package, with: ::API::Entities::Package, user: current_user, namespace: user_project.namespace
|
||||
end
|
||||
|
||||
desc 'Get the pipelines for a single project package' do
|
||||
detail 'This feature was introduced in GitLab 16.1'
|
||||
success code: 200, model: ::API::Entities::Package::Pipeline
|
||||
failure [
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not Found' }
|
||||
]
|
||||
tags %w[project_packages]
|
||||
end
|
||||
params do
|
||||
use :pagination
|
||||
requires :package_id, type: Integer, desc: 'The ID of a package'
|
||||
optional :cursor, type: String, desc: 'Cursor for obtaining the next set of records'
|
||||
# Overrides the original definition to add the `values: 1..20` restriction
|
||||
optional :per_page, type: Integer, default: 20,
|
||||
desc: 'Number of items per page', documentation: { example: 20 },
|
||||
values: 1..20
|
||||
end
|
||||
route_setting :authentication, job_token_allowed: true
|
||||
get ':id/packages/:package_id/pipelines' do
|
||||
not_found!('Package not found') unless package.default?
|
||||
|
||||
params[:pagination] = 'keyset' # keyset is the only available pagination
|
||||
pipelines = paginate_with_strategies(
|
||||
package.build_infos.without_empty_pipelines,
|
||||
paginator_params: { per_page: declared_params[:per_page], cursor: declared_params[:cursor] }
|
||||
) do |results|
|
||||
::Ci::Pipeline.id_in(results.map(&:pipeline_id)).select(PIPELINE_COLUMNS).order_id_desc
|
||||
end
|
||||
|
||||
present pipelines, with: ::API::Entities::Package::Pipeline, user: current_user
|
||||
end
|
||||
|
||||
desc 'Delete a project package' do
|
||||
detail 'This feature was introduced in GitLab 11.9'
|
||||
success code: 204
|
||||
|
|
@ -90,9 +131,6 @@ module API
|
|||
delete ':id/packages/:package_id' do
|
||||
authorize_destroy_package!(user_project)
|
||||
|
||||
package = ::Packages::PackageFinder
|
||||
.new(user_project, params[:package_id]).execute
|
||||
|
||||
destroy_conditionally!(package) do |package|
|
||||
::Packages::MarkPackageForDestructionService.new(container: package, current_user: current_user).execute
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,28 @@ module ExtractsRef
|
|||
InvalidPathError = Class.new(StandardError)
|
||||
BRANCH_REF_TYPE = 'heads'
|
||||
TAG_REF_TYPE = 'tags'
|
||||
REF_TYPES = [BRANCH_REF_TYPE, TAG_REF_TYPE].freeze
|
||||
|
||||
def self.ref_type(type)
|
||||
return unless REF_TYPES.include?(type)
|
||||
|
||||
type
|
||||
end
|
||||
|
||||
def self.qualify_ref(ref, type)
|
||||
validated_type = ref_type(type)
|
||||
return ref unless validated_type
|
||||
|
||||
%(refs/#{validated_type}/#{ref})
|
||||
end
|
||||
|
||||
def self.unqualify_ref(ref, type)
|
||||
validated_type = ref_type(type)
|
||||
return ref unless validated_type
|
||||
|
||||
ref.sub(%r{^refs/#{validated_type}/}, '')
|
||||
end
|
||||
|
||||
# Given a string containing both a Git tree-ish, such as a branch or tag, and
|
||||
# a filesystem path joined by forward slashes, attempts to separate the two.
|
||||
#
|
||||
|
|
@ -60,7 +82,6 @@ module ExtractsRef
|
|||
#
|
||||
# If the :id parameter appears to be requesting a specific response format,
|
||||
# that will be handled as well.
|
||||
#
|
||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
def assign_ref_vars
|
||||
@id, @ref, @path = extract_ref_path
|
||||
|
|
@ -70,7 +91,7 @@ module ExtractsRef
|
|||
return unless @ref.present?
|
||||
|
||||
@commit = if ref_type
|
||||
@fully_qualified_ref = %(refs/#{ref_type}/#{@ref})
|
||||
@fully_qualified_ref = ExtractsRef.qualify_ref(@ref, ref_type)
|
||||
@repo.commit(@fully_qualified_ref)
|
||||
else
|
||||
@repo.commit(@ref)
|
||||
|
|
@ -90,9 +111,7 @@ module ExtractsRef
|
|||
end
|
||||
|
||||
def ref_type
|
||||
return unless params[:ref_type].present?
|
||||
|
||||
params[:ref_type] == TAG_REF_TYPE ? TAG_REF_TYPE : BRANCH_REF_TYPE
|
||||
ExtractsRef.ref_type(params[:ref_type])
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -156,6 +175,7 @@ module ExtractsRef
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# deprecated in favor of ExtractsRef::RequestedRef
|
||||
def ambiguous_ref?(project, ref)
|
||||
return false unless ref
|
||||
return true if project.repository.ambiguous_ref?(ref)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module Gitlab
|
|||
include Gitlab::EncodingHelper
|
||||
extend Gitlab::Git::WrapsGitalyErrors
|
||||
|
||||
attr_accessor :id, :type, :mode, :commit_id, :submodule_url
|
||||
attr_accessor :id, :type, :mode, :commit_id, :submodule_url, :ref_type
|
||||
attr_writer :name, :path, :flat_path
|
||||
|
||||
class << self
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ module Gitlab
|
|||
SUPPORTED_ORDERING = {
|
||||
Group => { name: :asc },
|
||||
AuditEvent => { id: :desc },
|
||||
::Ci::Build => { id: :desc }
|
||||
::Ci::Build => { id: :desc },
|
||||
::Packages::BuildInfo => { id: :desc }
|
||||
}.freeze
|
||||
|
||||
# Relation types that are enforced in this list
|
||||
|
|
|
|||
|
|
@ -12,62 +12,56 @@ module Gitlab
|
|||
end
|
||||
|
||||
def calculate
|
||||
cte = if Feature.enabled?(:linear_project_authorization, user)
|
||||
linear_cte
|
||||
else
|
||||
recursive_cte
|
||||
end
|
||||
if Feature.enabled?(:compare_project_authorization_linear_cte, user)
|
||||
linear_relation = calculate_with_linear_query
|
||||
recursive_relation = calculate_with_recursive_query
|
||||
recursive_set = Set.new(recursive_relation.to_a.pluck(:project_id, :access_level))
|
||||
linear_set = Set.new(linear_relation.to_a.pluck(:project_id, :access_level))
|
||||
if linear_set == recursive_set
|
||||
Gitlab::AppJsonLogger.info(event: 'linear_authorized_projects_check',
|
||||
user_id: user.id,
|
||||
matching_results: true)
|
||||
return calculate_with_linear_query
|
||||
else
|
||||
Gitlab::AppJsonLogger.warn(event: 'linear_authorized_projects_check',
|
||||
user_id: user.id,
|
||||
matching_results: false)
|
||||
end
|
||||
end
|
||||
|
||||
cte_alias = cte.table.alias(Group.table_name)
|
||||
projects = Project.arel_table
|
||||
links = ProjectGroupLink.arel_table
|
||||
|
||||
relations = [
|
||||
# The project a user has direct access to.
|
||||
user.projects_with_active_memberships.select_for_project_authorization,
|
||||
|
||||
# The personal projects of the user.
|
||||
user.personal_projects.select_project_owner_for_project_authorization,
|
||||
|
||||
# Projects that belong directly to any of the groups the user has
|
||||
# access to.
|
||||
Namespace
|
||||
.unscoped
|
||||
.select([alias_as_column(projects[:id], 'project_id'),
|
||||
cte_alias[:access_level]])
|
||||
.from(cte_alias)
|
||||
.joins(:projects),
|
||||
|
||||
# Projects shared with any of the namespaces the user has access to.
|
||||
Namespace
|
||||
.unscoped
|
||||
.select([
|
||||
links[:project_id],
|
||||
least(cte_alias[:access_level], links[:group_access], 'access_level')
|
||||
])
|
||||
.from(cte_alias)
|
||||
.joins('INNER JOIN project_group_links ON project_group_links.group_id = namespaces.id')
|
||||
.joins('INNER JOIN projects ON projects.id = project_group_links.project_id')
|
||||
.joins('INNER JOIN namespaces p_ns ON p_ns.id = projects.namespace_id')
|
||||
.where('p_ns.share_with_group_lock IS FALSE')
|
||||
]
|
||||
Gitlab::AppJsonLogger.info(event: 'linear_authorized_projects_check_with_flag',
|
||||
feature_flag_status: Feature.enabled?(:linear_project_authorization, user))
|
||||
|
||||
if Feature.enabled?(:linear_project_authorization, user)
|
||||
ProjectAuthorization
|
||||
.unscoped
|
||||
.with(cte.to_arel)
|
||||
.select_from_union(relations)
|
||||
calculate_with_linear_query
|
||||
else
|
||||
ProjectAuthorization
|
||||
.unscoped
|
||||
.with
|
||||
.recursive(cte.to_arel)
|
||||
.select_from_union(relations)
|
||||
calculate_with_recursive_query
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def calculate_with_linear_query
|
||||
cte = linear_cte
|
||||
cte_alias = cte.table.alias(Group.table_name)
|
||||
|
||||
ProjectAuthorization
|
||||
.unscoped
|
||||
.with(cte.to_arel)
|
||||
.select_from_union(relations(cte_alias: cte_alias))
|
||||
end
|
||||
|
||||
def calculate_with_recursive_query
|
||||
cte = recursive_cte
|
||||
cte_alias = cte.table.alias(Group.table_name)
|
||||
|
||||
ProjectAuthorization
|
||||
.unscoped
|
||||
.with
|
||||
.recursive(cte.to_arel)
|
||||
.select_from_union(relations(cte_alias: cte_alias))
|
||||
end
|
||||
|
||||
# Builds a recursive CTE that gets all the groups the current user has
|
||||
# access to, including any nested groups and any shared groups.
|
||||
def recursive_cte
|
||||
|
|
@ -83,9 +77,11 @@ module Gitlab
|
|||
|
||||
# Namespaces shared with any of the group
|
||||
cte << Group.select([namespaces[:id],
|
||||
least(members[:access_level],
|
||||
group_group_links[:group_access],
|
||||
'access_level')])
|
||||
least(
|
||||
members[:access_level],
|
||||
group_group_links[:group_access],
|
||||
'access_level'
|
||||
)])
|
||||
.joins(join_group_group_links)
|
||||
.joins(join_members_on_group_group_links)
|
||||
|
||||
|
|
@ -187,5 +183,45 @@ module Gitlab
|
|||
def alias_as_column(value, alias_to)
|
||||
Arel::Nodes::As.new(value, Arel::Nodes::SqlLiteral.new(alias_to))
|
||||
end
|
||||
|
||||
def relations(cte_alias:)
|
||||
[
|
||||
user.projects_with_active_memberships.select_for_project_authorization,
|
||||
user.personal_projects.select_project_owner_for_project_authorization,
|
||||
projects_belonging_directy_to_any_groups_user_has_access_to(cte_alias: cte_alias),
|
||||
projects_shared_with_namespaces_user_has_access_to(cte_alias: cte_alias)
|
||||
]
|
||||
end
|
||||
|
||||
def projects_shared_with_namespaces_user_has_access_to(cte_alias:)
|
||||
Namespace
|
||||
.unscoped
|
||||
.select([
|
||||
links[:project_id],
|
||||
least(cte_alias[:access_level], links[:group_access], 'access_level')
|
||||
])
|
||||
.from(cte_alias)
|
||||
.joins('INNER JOIN project_group_links ON project_group_links.group_id = namespaces.id')
|
||||
.joins('INNER JOIN projects ON projects.id = project_group_links.project_id')
|
||||
.joins('INNER JOIN namespaces p_ns ON p_ns.id = projects.namespace_id')
|
||||
.where('p_ns.share_with_group_lock IS FALSE')
|
||||
end
|
||||
|
||||
def projects_belonging_directy_to_any_groups_user_has_access_to(cte_alias:)
|
||||
Namespace
|
||||
.unscoped
|
||||
.select([alias_as_column(projects[:id], 'project_id'),
|
||||
cte_alias[:access_level]])
|
||||
.from(cte_alias)
|
||||
.joins(:projects)
|
||||
end
|
||||
|
||||
def projects
|
||||
Project.arel_table
|
||||
end
|
||||
|
||||
def links
|
||||
ProjectGroupLink.arel_table
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16799,6 +16799,9 @@ msgstr ""
|
|||
msgid "Email display name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Email must be provided."
|
||||
msgstr ""
|
||||
|
||||
msgid "Email not verified. Please verify your email in Salesforce."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -18803,6 +18806,9 @@ msgstr ""
|
|||
msgid "Failed to save merge conflicts resolutions. Please try again!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to save namespace commit email."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to save new settings"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -27346,7 +27352,7 @@ msgstr ""
|
|||
msgid "Loading snippet"
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading the GitLab IDE..."
|
||||
msgid "Loading the GitLab IDE"
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading, please wait."
|
||||
|
|
@ -29705,6 +29711,12 @@ msgstr ""
|
|||
msgid "Namespace Limits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Namespace doesn't exist or you don't have permission."
|
||||
msgstr ""
|
||||
|
||||
msgid "Namespace must be provided."
|
||||
msgstr ""
|
||||
|
||||
msgid "Namespace or group to import repository into does not exist."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -49493,6 +49505,9 @@ msgstr ""
|
|||
msgid "User does not have permission to create a Security Policy project."
|
||||
msgstr ""
|
||||
|
||||
msgid "User doesn't exist or you don't have permission to change namespace commit emails."
|
||||
msgstr ""
|
||||
|
||||
msgid "User has already been deactivated"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@
|
|||
module QA
|
||||
RSpec.describe 'Create' do
|
||||
describe 'Branch Rules Overview', product_group: :source_code,
|
||||
feature_flag: {
|
||||
name: 'branch_rules',
|
||||
scope: :project
|
||||
},
|
||||
quarantine: {
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/403583',
|
||||
type: :flaky
|
||||
|
|
@ -22,8 +18,6 @@ module QA
|
|||
end
|
||||
|
||||
before do
|
||||
Runtime::Feature.enable(:branch_rules, project: project)
|
||||
|
||||
Flow::Login.sign_in
|
||||
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
|
|
@ -35,10 +29,6 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
after do
|
||||
Runtime::Feature.disable(:branch_rules, project: project)
|
||||
end
|
||||
|
||||
it 'adds a new branch rule', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/397587' do
|
||||
project.visit!
|
||||
|
||||
|
|
|
|||
|
|
@ -45,14 +45,5 @@ RSpec.describe 'Projects > Settings > Repository > Branch rules settings', featu
|
|||
expect(page).to have_content('Branch rules')
|
||||
end
|
||||
end
|
||||
|
||||
context 'branch_rules feature flag disabled' do
|
||||
it 'does not render branch rules content' do
|
||||
stub_feature_flags(branch_rules: false)
|
||||
request
|
||||
|
||||
expect(page).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :g
|
|||
let(:role) { :developer }
|
||||
|
||||
before do
|
||||
stub_feature_flags(branch_rules: false)
|
||||
stub_feature_flags(mirror_only_branches_match_regex: false)
|
||||
project.add_role(user, role)
|
||||
sign_in(user)
|
||||
|
|
@ -43,15 +42,7 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :g
|
|||
end
|
||||
|
||||
context 'Branch rules', :js do
|
||||
context 'branch_rules feature flag disabled', :js do
|
||||
it 'does not render branch rules settings' do
|
||||
visit project_settings_repository_path(project)
|
||||
expect(page).not_to have_content('Branch rules')
|
||||
end
|
||||
end
|
||||
|
||||
it 'renders branch rules settings' do
|
||||
stub_feature_flags(branch_rules: true)
|
||||
visit project_settings_repository_path(project)
|
||||
expect(page).to have_content('Branch rules')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.shared_examples 'Signup name validation' do |field, max_length, label|
|
||||
flag_values = [true, false]
|
||||
flag_values.each do |val|
|
||||
shared_examples 'signup validation' do
|
||||
before do
|
||||
stub_feature_flags(restyle_login_page: val)
|
||||
visit new_user_registration_path
|
||||
end
|
||||
|
||||
|
|
@ -42,6 +40,18 @@ RSpec.shared_examples 'Signup name validation' do |field, max_length, label|
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'signup validation'
|
||||
|
||||
# Inline `shared_example 'signup validation'` again after feature flag
|
||||
# `restyle_login_page` was removed.
|
||||
context 'with feature flag restyle_login_page disabled' do
|
||||
before do
|
||||
stub_feature_flags(restyle_login_page: false)
|
||||
end
|
||||
|
||||
include_examples 'signup validation'
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
||||
|
|
@ -49,25 +59,32 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
|||
|
||||
let(:new_user) { build_stubbed(:user) }
|
||||
|
||||
def fill_in_signup_form
|
||||
fill_in 'new_user_username', with: new_user.username
|
||||
fill_in 'new_user_email', with: new_user.email
|
||||
fill_in 'new_user_first_name', with: new_user.first_name
|
||||
fill_in 'new_user_last_name', with: new_user.last_name
|
||||
fill_in 'new_user_password', with: new_user.password
|
||||
let(:terms_text) do
|
||||
<<~TEXT.squish
|
||||
By clicking Register or registering through a third party you accept the
|
||||
Terms of Use and acknowledge the Privacy Policy and Cookie Policy
|
||||
TEXT
|
||||
end
|
||||
|
||||
def confirm_email
|
||||
new_user_token = User.find_by_email(new_user.email).confirmation_token
|
||||
shared_examples 'signup process' do
|
||||
def fill_in_signup_form
|
||||
fill_in 'new_user_username', with: new_user.username
|
||||
fill_in 'new_user_email', with: new_user.email
|
||||
fill_in 'new_user_first_name', with: new_user.first_name
|
||||
fill_in 'new_user_last_name', with: new_user.last_name
|
||||
fill_in 'new_user_password', with: new_user.password
|
||||
|
||||
visit user_confirmation_path(confirmation_token: new_user_token)
|
||||
end
|
||||
wait_for_all_requests
|
||||
end
|
||||
|
||||
def confirm_email
|
||||
new_user_token = User.find_by_email(new_user.email).confirmation_token
|
||||
|
||||
visit user_confirmation_path(confirmation_token: new_user_token)
|
||||
end
|
||||
|
||||
flag_values = [true, false]
|
||||
flag_values.each do |val|
|
||||
before do
|
||||
stub_feature_flags(arkose_labs_signup_challenge: false)
|
||||
stub_feature_flags(restyle_login_page: val)
|
||||
stub_application_setting(require_admin_approval_after_user_signup: false)
|
||||
end
|
||||
|
||||
|
|
@ -162,7 +179,8 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
|||
expect(page).to have_content("Invalid input, please avoid emojis")
|
||||
end
|
||||
|
||||
it 'shows a pending message if the username availability is being fetched' do
|
||||
it 'shows a pending message if the username availability is being fetched',
|
||||
quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/31484' do
|
||||
fill_in 'new_user_username', with: 'new-user'
|
||||
|
||||
expect(find('.username > .validation-pending')).not_to have_css '.hide'
|
||||
|
|
@ -263,7 +281,10 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
|||
|
||||
expect { click_button 'Register' }.to change { User.count }.by(1)
|
||||
expect(page).to have_current_path new_user_session_path, ignore_query: true
|
||||
expect(page).to have_content("You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator")
|
||||
expect(page).to have_content(<<~TEXT.squish)
|
||||
You have signed up successfully. However, we could not sign you in
|
||||
because your account is awaiting approval from your GitLab administrator
|
||||
TEXT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -305,13 +326,26 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
|||
|
||||
it 'renders text that the user confirms terms by signing in' do
|
||||
visit new_user_registration_path
|
||||
expect(page).to have_content(/By clicking Register, I agree that I have read and accepted the Terms of Use and Privacy Policy/)
|
||||
expect(page).to have_content(terms_text)
|
||||
|
||||
fill_in_signup_form
|
||||
click_button 'Register'
|
||||
|
||||
expect(page).to have_current_path users_sign_up_welcome_path, ignore_query: true
|
||||
expect(page).to have_current_path(users_sign_up_welcome_path), ignore_query: true
|
||||
visit new_project_path
|
||||
|
||||
select 'Software Developer', from: 'user_role'
|
||||
click_button 'Get started!'
|
||||
|
||||
created_user = User.find_by_username(new_user.username)
|
||||
|
||||
expect(created_user.software_developer_role?).to be_truthy
|
||||
expect(created_user.setup_for_company).to be_nil
|
||||
expect(page).to have_current_path(new_project_path)
|
||||
end
|
||||
|
||||
it_behaves_like 'Signup name validation', 'new_user_first_name', 127, 'First name'
|
||||
it_behaves_like 'Signup name validation', 'new_user_last_name', 127, 'Last name'
|
||||
end
|
||||
|
||||
context 'when reCAPTCHA and invisible captcha are enabled' do
|
||||
|
|
@ -337,7 +371,8 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
|||
|
||||
expect { click_button 'Register' }.not_to change { User.count }
|
||||
expect(page).to have_content(_('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'))
|
||||
expect(page).to have_content("Minimum length is #{Gitlab::CurrentSettings.minimum_password_length} characters")
|
||||
expect(page).to have_content(
|
||||
"Minimum length is #{Gitlab::CurrentSettings.minimum_password_length} characters")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -357,7 +392,6 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
|||
visit new_user_registration_path
|
||||
|
||||
fill_in_signup_form
|
||||
wait_for_all_requests
|
||||
|
||||
click_button 'Register'
|
||||
|
||||
|
|
@ -393,34 +427,22 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when terms are enforced' do
|
||||
include_examples 'signup process'
|
||||
|
||||
# Inline `shared_example 'signup process'` again after feature flag
|
||||
# `restyle_login_page` was removed.
|
||||
context 'with feature flag restyle_login_page disabled' do
|
||||
let(:terms_text) do
|
||||
<<~TEXT.squish
|
||||
By clicking Register, I agree that I have read and accepted the Terms of
|
||||
Use and Privacy Policy
|
||||
TEXT
|
||||
end
|
||||
|
||||
before do
|
||||
enforce_terms
|
||||
stub_feature_flags(restyle_login_page: false)
|
||||
end
|
||||
|
||||
it 'renders text that the user confirms terms by signing in' do
|
||||
visit new_user_registration_path
|
||||
|
||||
expect(page).to have_content(/By clicking Register, I agree that I have read and accepted the Terms of Use and Privacy Policy/)
|
||||
|
||||
fill_in_signup_form
|
||||
click_button 'Register'
|
||||
|
||||
visit new_project_path
|
||||
|
||||
expect(page).to have_current_path(users_sign_up_welcome_path)
|
||||
|
||||
select 'Software Developer', from: 'user_role'
|
||||
click_button 'Get started!'
|
||||
|
||||
created_user = User.find_by_username(new_user.username)
|
||||
|
||||
expect(created_user.software_developer_role?).to be_truthy
|
||||
expect(created_user.setup_for_company).to be_nil
|
||||
expect(page).to have_current_path(new_project_path)
|
||||
end
|
||||
|
||||
it_behaves_like 'Signup name validation', 'new_user_first_name', 127, 'First name'
|
||||
it_behaves_like 'Signup name validation', 'new_user_last_name', 127, 'Last name'
|
||||
include_examples 'signup process'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "../pipeline.json"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Mutations::Users::SetNamespaceCommitEmail, feature_category: :user_profile do
|
||||
include GraphqlHelpers
|
||||
|
||||
let(:current_user) { create(:user) }
|
||||
let(:group) { create(:group) }
|
||||
let(:email) { create(:email, user: current_user) }
|
||||
let(:input) { {} }
|
||||
let(:namespace_id) { group.to_global_id }
|
||||
let(:email_id) { email.to_global_id }
|
||||
|
||||
shared_examples 'success' do
|
||||
it 'creates namespace commit email with correct values' do
|
||||
expect(resolve_mutation[:namespace_commit_email])
|
||||
.to have_attributes({ namespace_id: namespace_id.model_id.to_i, email_id: email_id.model_id.to_i })
|
||||
end
|
||||
end
|
||||
|
||||
describe '#resolve' do
|
||||
subject(:resolve_mutation) do
|
||||
described_class.new(object: nil, context: { current_user: current_user }, field: nil).resolve(
|
||||
namespace_id: namespace_id,
|
||||
email_id: email_id
|
||||
)
|
||||
end
|
||||
|
||||
context 'when current_user does not have permission' do
|
||||
it 'raises an error' do
|
||||
expect { resolve_mutation }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
.with_message(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user has permission' do
|
||||
before do
|
||||
group.add_reporter(current_user)
|
||||
end
|
||||
|
||||
context 'when the email does not belong to the target user' do
|
||||
let(:email_id) { create(:email).to_global_id }
|
||||
|
||||
it 'returns the validation error' do
|
||||
expect(resolve_mutation[:errors]).to contain_exactly("Email must be provided.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when namespace is a group' do
|
||||
it_behaves_like 'success'
|
||||
end
|
||||
|
||||
context 'when namespace is a user' do
|
||||
let(:namespace_id) { current_user.namespace.to_global_id }
|
||||
|
||||
it_behaves_like 'success'
|
||||
end
|
||||
|
||||
context 'when namespace is a project' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:namespace_id) { project.project_namespace.to_global_id }
|
||||
|
||||
before do
|
||||
project.add_reporter(current_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'success'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
specify { expect(described_class).to require_graphql_authorizations(:read_namespace) }
|
||||
end
|
||||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::BlobsResolver do
|
||||
RSpec.describe Resolvers::BlobsResolver, feature_category: :source_code_management do
|
||||
include GraphqlHelpers
|
||||
include RepoHelpers
|
||||
|
||||
describe '.resolver_complexity' do
|
||||
it 'adds one per path being resolved' do
|
||||
|
|
@ -59,15 +60,89 @@ RSpec.describe Resolvers::BlobsResolver do
|
|||
end
|
||||
end
|
||||
|
||||
context 'specifying a different ref' do
|
||||
context 'when specifying a branch ref' do
|
||||
let(:ref) { 'add-pdf-file' }
|
||||
let(:args) { { paths: paths, ref: ref, ref_type: ref_type } }
|
||||
let(:paths) { ['files/pdf/test.pdf', 'README.md'] }
|
||||
|
||||
it 'returns the specified blobs for that ref' do
|
||||
is_expected.to contain_exactly(
|
||||
have_attributes(path: 'files/pdf/test.pdf'),
|
||||
have_attributes(path: 'README.md')
|
||||
)
|
||||
context 'and no ref_type is specified' do
|
||||
let(:ref_type) { nil }
|
||||
|
||||
it 'returns the specified blobs for that ref' do
|
||||
is_expected.to contain_exactly(
|
||||
have_attributes(path: 'files/pdf/test.pdf'),
|
||||
have_attributes(path: 'README.md')
|
||||
)
|
||||
end
|
||||
|
||||
context 'and a tag with the same name exists' do
|
||||
let(:ref) { SecureRandom.uuid }
|
||||
|
||||
before do
|
||||
project.repository.create_branch(ref)
|
||||
create_file_in_repo(project, ref, ref, 'branch_file', 'Test file', commit_message: 'Add new content')
|
||||
project.repository.add_tag(project.owner, sample_commit.id, ref)
|
||||
end
|
||||
|
||||
it 'returns the specified blobs for the tag' do
|
||||
is_expected.to contain_exactly(
|
||||
have_attributes(path: 'README.md')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and ref_type is for branches' do
|
||||
let(:args) { { paths: paths, ref: ref, ref_type: 'heads' } }
|
||||
|
||||
it 'returns nothing' do
|
||||
is_expected.to contain_exactly(
|
||||
have_attributes(path: 'files/pdf/test.pdf'),
|
||||
have_attributes(path: 'README.md')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and ref_type is for tags' do
|
||||
let(:args) { { paths: paths, ref: ref, ref_type: 'tags' } }
|
||||
|
||||
it 'returns nothing' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when specifying a tag ref' do
|
||||
let(:ref) { 'v1.0.0' }
|
||||
|
||||
let(:args) { { paths: paths, ref: ref, ref_type: ref_type } }
|
||||
|
||||
context 'and no ref_type is specified' do
|
||||
let(:ref_type) { nil }
|
||||
|
||||
it 'returns the specified blobs for that ref' do
|
||||
is_expected.to contain_exactly(
|
||||
have_attributes(path: 'README.md')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and ref_type is for tags' do
|
||||
let(:ref_type) { 'tags' }
|
||||
|
||||
it 'returns the specified blobs for that ref' do
|
||||
is_expected.to contain_exactly(
|
||||
have_attributes(path: 'README.md')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and ref_type is for branches' do
|
||||
let(:ref_type) { 'heads' }
|
||||
|
||||
it 'returns nothing' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -61,5 +61,29 @@ RSpec.describe Resolvers::LastCommitResolver do
|
|||
expect(commit).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the ref is ambiguous' do
|
||||
let(:ambiguous_ref) { 'v1.0.0' }
|
||||
|
||||
before do
|
||||
project.repository.create_branch(ambiguous_ref)
|
||||
end
|
||||
|
||||
context 'when tree is for a tag' do
|
||||
let(:tree) { repository.tree(ambiguous_ref, ref_type: 'tags') }
|
||||
|
||||
it 'resolves commit' do
|
||||
expect(commit.id).to eq(repository.find_tag(ambiguous_ref).dereferenced_target.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tree is for a branch' do
|
||||
let(:tree) { repository.tree(ambiguous_ref, ref_type: 'heads') }
|
||||
|
||||
it 'resolves commit' do
|
||||
expect(commit.id).to eq(repository.find_branch(ambiguous_ref).target)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -57,5 +57,64 @@ RSpec.describe ExtractsRef do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#ref_type' do
|
||||
let(:params) { ActionController::Parameters.new(ref_type: 'heads') }
|
||||
|
||||
it 'delegates to .ref_type' do
|
||||
expect(described_class).to receive(:ref_type).with('heads')
|
||||
ref_type
|
||||
end
|
||||
end
|
||||
|
||||
describe '.ref_type' do
|
||||
subject { described_class.ref_type(ref_type) }
|
||||
|
||||
context 'when ref_type is nil' do
|
||||
let(:ref_type) { nil }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
|
||||
context 'when ref_type is heads' do
|
||||
let(:ref_type) { 'heads' }
|
||||
|
||||
it { is_expected.to eq('heads') }
|
||||
end
|
||||
|
||||
context 'when ref_type is tags' do
|
||||
let(:ref_type) { 'tags' }
|
||||
|
||||
it { is_expected.to eq('tags') }
|
||||
end
|
||||
|
||||
context 'when ref_type is invalid' do
|
||||
let(:ref_type) { 'invalid' }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.qualify_ref' do
|
||||
subject { described_class.qualify_ref(ref, ref_type) }
|
||||
|
||||
context 'when ref_type is nil' do
|
||||
let(:ref_type) { nil }
|
||||
|
||||
it { is_expected.to eq(ref) }
|
||||
end
|
||||
|
||||
context 'when ref_type valid' do
|
||||
let(:ref_type) { 'heads' }
|
||||
|
||||
it { is_expected.to eq("refs/#{ref_type}/#{ref}") }
|
||||
end
|
||||
|
||||
context 'when ref_type is invalid' do
|
||||
let(:ref_type) { 'invalid' }
|
||||
|
||||
it { is_expected.to eq(ref) }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'extracts refs'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ RSpec.describe Gitlab::Pagination::CursorBasedKeyset do
|
|||
expect(subject.available_for_type?(Ci::Build.all)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true for Packages::BuildInfo' do
|
||||
expect(subject.available_for_type?(Packages::BuildInfo.all)).to be_truthy
|
||||
end
|
||||
|
||||
it 'return false for other types of relations' do
|
||||
expect(subject.available_for_type?(User.all)).to be_falsey
|
||||
end
|
||||
|
|
@ -56,6 +60,7 @@ RSpec.describe Gitlab::Pagination::CursorBasedKeyset do
|
|||
it 'return false for other types of relations' do
|
||||
expect(subject.available?(cursor_based_request_context, User.all)).to be_falsey
|
||||
expect(subject.available?(cursor_based_request_context, Ci::Build.all)).to be_falsey
|
||||
expect(subject.available?(cursor_based_request_context, Packages::BuildInfo.all)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -70,6 +75,10 @@ RSpec.describe Gitlab::Pagination::CursorBasedKeyset do
|
|||
it 'returns true for AuditEvent' do
|
||||
expect(subject.available?(cursor_based_request_context, AuditEvent.all)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true for Packages::BuildInfo' do
|
||||
expect(subject.available?(cursor_based_request_context, Packages::BuildInfo.all)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'with other order-by columns' do
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ RSpec.describe Gitlab::ProjectAuthorizations, feature_category: :system_access d
|
|||
end
|
||||
end
|
||||
|
||||
let(:service) { described_class.new(user) }
|
||||
|
||||
subject(:authorizations) do
|
||||
described_class.new(user).calculate
|
||||
service.calculate
|
||||
end
|
||||
|
||||
# Inline this shared example while cleaning up feature flag linear_project_authorization
|
||||
|
|
@ -421,7 +423,34 @@ RSpec.describe Gitlab::ProjectAuthorizations, feature_category: :system_access d
|
|||
end
|
||||
end
|
||||
|
||||
context 'when feature_flag linear_project_authorization_is disabled' do
|
||||
context 'it compares values for correctness' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
context 'when values returned by the queries are the same' do
|
||||
it 'logs a message indicating that the values are the same' do
|
||||
expect(Gitlab::AppJsonLogger).to receive(:info).with(event: 'linear_authorized_projects_check',
|
||||
user_id: user.id,
|
||||
matching_results: true)
|
||||
service.calculate
|
||||
end
|
||||
end
|
||||
|
||||
context 'when values returned by queries are diffrent' do
|
||||
before do
|
||||
create(:project_authorization)
|
||||
allow(service).to receive(:calculate_with_linear_query).and_return(ProjectAuthorization.all)
|
||||
end
|
||||
|
||||
it 'logs a message indicating that the values are different' do
|
||||
expect(Gitlab::AppJsonLogger).to receive(:warn).with(event: 'linear_authorized_projects_check',
|
||||
user_id: user.id,
|
||||
matching_results: false)
|
||||
service.calculate
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature_flag linear_project_authorization is disabled' do
|
||||
before do
|
||||
stub_feature_flags(linear_project_authorization: false)
|
||||
end
|
||||
|
|
@ -429,5 +458,22 @@ RSpec.describe Gitlab::ProjectAuthorizations, feature_category: :system_access d
|
|||
it_behaves_like 'project authorizations'
|
||||
end
|
||||
|
||||
context 'when feature_flag compare_project_authorization_linear_cte is disabled' do
|
||||
before do
|
||||
stub_feature_flags(compare_project_authorization_linear_cte: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'project authorizations'
|
||||
end
|
||||
|
||||
context 'when feature_flag linear_project_authorization and compare_project_authorization_linear_cte are disabled' do
|
||||
before do
|
||||
stub_feature_flags(linear_project_authorization: false)
|
||||
stub_feature_flags(compare_project_authorization_linear_cte: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'project authorizations'
|
||||
end
|
||||
|
||||
it_behaves_like 'project authorizations'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -253,10 +253,12 @@ RSpec.describe UserPolicy do
|
|||
|
||||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed(:read_user_email_address) }
|
||||
it { is_expected.to be_allowed(:admin_user_email_address) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it { is_expected.not_to be_allowed(:read_user_email_address) }
|
||||
it { is_expected.not_to be_allowed(:admin_user_email_address) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -265,10 +267,12 @@ RSpec.describe UserPolicy do
|
|||
subject { described_class.new(current_user, current_user) }
|
||||
|
||||
it { is_expected.to be_allowed(:read_user_email_address) }
|
||||
it { is_expected.to be_allowed(:admin_user_email_address) }
|
||||
end
|
||||
|
||||
context "requesting a different user's" do
|
||||
it { is_expected.not_to be_allowed(:read_user_email_address) }
|
||||
it { is_expected.not_to be_allowed(:admin_user_email_address) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,6 +31,32 @@ RSpec.describe BlobPresenter do
|
|||
it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/update/#{blob.commit_id}/#{blob.path}") }
|
||||
end
|
||||
|
||||
context 'when blob has ref_type' do
|
||||
before do
|
||||
blob.ref_type = 'heads'
|
||||
end
|
||||
|
||||
describe '#web_url' do
|
||||
it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}?ref_type=heads") }
|
||||
end
|
||||
|
||||
describe '#web_path' do
|
||||
it { expect(presenter.web_path).to eq("/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}?ref_type=heads") }
|
||||
end
|
||||
|
||||
describe '#edit_blob_path' do
|
||||
it { expect(presenter.edit_blob_path).to eq("/#{project.full_path}/-/edit/#{blob.commit_id}/#{blob.path}?ref_type=heads") }
|
||||
end
|
||||
|
||||
describe '#raw_path' do
|
||||
it { expect(presenter.raw_path).to eq("/#{project.full_path}/-/raw/#{blob.commit_id}/#{blob.path}?ref_type=heads") }
|
||||
end
|
||||
|
||||
describe '#replace_path' do
|
||||
it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/update/#{blob.commit_id}/#{blob.path}?ref_type=heads") }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_current_user_push_to_branch' do
|
||||
let(:branch_exists) { true }
|
||||
|
||||
|
|
|
|||
|
|
@ -17,4 +17,20 @@ RSpec.describe TreeEntryPresenter do
|
|||
describe '#web_path' do
|
||||
it { expect(presenter.web_path).to eq("/#{project.full_path}/-/tree/#{tree.commit_id}/#{tree.path}") }
|
||||
end
|
||||
|
||||
context 'when blob has ref_type' do
|
||||
before do
|
||||
tree.ref_type = 'heads'
|
||||
end
|
||||
|
||||
describe '.web_url' do
|
||||
it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/tree/#{tree.commit_id}/#{tree.path}?ref_type=heads") }
|
||||
end
|
||||
|
||||
describe '#web_path' do
|
||||
it {
|
||||
expect(presenter.web_path).to eq("/#{project.full_path}/-/tree/#{tree.commit_id}/#{tree.path}?ref_type=heads")
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Setting namespace commit email', feature_category: :user_profile do
|
||||
include GraphqlHelpers
|
||||
|
||||
let(:current_user) { create(:user) }
|
||||
let(:group) { create(:group, :public) }
|
||||
let(:email) { create(:email, :confirmed, user: current_user) }
|
||||
let(:input) { {} }
|
||||
let(:namespace_id) { group.to_global_id }
|
||||
let(:email_id) { email.to_global_id }
|
||||
|
||||
let(:resource_or_permission_error) do
|
||||
"The resource that you are attempting to access does not exist or you don't have permission to perform this action"
|
||||
end
|
||||
|
||||
let(:mutation) do
|
||||
variables = {
|
||||
namespace_id: namespace_id,
|
||||
email_id: email_id
|
||||
}
|
||||
graphql_mutation(:user_set_namespace_commit_email, variables.merge(input),
|
||||
<<-QL.strip_heredoc
|
||||
namespaceCommitEmail {
|
||||
email {
|
||||
id
|
||||
}
|
||||
}
|
||||
errors
|
||||
QL
|
||||
)
|
||||
end
|
||||
|
||||
def mutation_response
|
||||
graphql_mutation_response(:user_set_namespace_commit_email)
|
||||
end
|
||||
|
||||
shared_examples 'success' do
|
||||
it 'creates a namespace commit email' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(mutation_response.dig('namespaceCommitEmail', 'email', 'id')).to eq(email.to_global_id.to_s)
|
||||
expect(graphql_errors).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
group.add_reporter(current_user)
|
||||
end
|
||||
|
||||
context 'when current_user is nil' do
|
||||
it 'returns the top level error' do
|
||||
post_graphql_mutation(mutation, current_user: nil)
|
||||
|
||||
expect(graphql_errors.first).to match a_hash_including(
|
||||
'message' => resource_or_permission_error)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user cannot access the namespace' do
|
||||
let(:namespace_id) { create(:group).to_global_id }
|
||||
|
||||
it 'returns the top level error' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(graphql_errors).not_to be_empty
|
||||
expect(graphql_errors.first).to match a_hash_including(
|
||||
'message' => resource_or_permission_error)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the service returns an error' do
|
||||
let(:email_id) { create(:email).to_global_id }
|
||||
|
||||
it 'returns the error' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(mutation_response['errors']).to contain_exactly("Email must be provided.")
|
||||
expect(mutation_response['namespaceCommitEmail']).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when namespace is a group' do
|
||||
it_behaves_like 'success'
|
||||
end
|
||||
|
||||
context 'when namespace is a user' do
|
||||
let(:namespace_id) { current_user.namespace.to_global_id }
|
||||
|
||||
it_behaves_like 'success'
|
||||
end
|
||||
|
||||
context 'when namespace is a project' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:namespace_id) { project.project_namespace.to_global_id }
|
||||
|
||||
before do
|
||||
project.add_reporter(current_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'success'
|
||||
end
|
||||
end
|
||||
|
|
@ -3,9 +3,11 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::ProjectPackages, feature_category: :package_registry do
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let_it_be_with_reload(:project) { create(:project, :public) }
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let!(:package1) { create(:npm_package, :last_downloaded_at, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
|
||||
let(:package_url) { "/projects/#{project.id}/packages/#{package1.id}" }
|
||||
let!(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
|
||||
|
|
@ -101,7 +103,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
|
|||
end
|
||||
|
||||
context 'project is private' do
|
||||
let(:project) { create(:project, :private) }
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
|
||||
context 'for unauthenticated user' do
|
||||
it_behaves_like 'rejects packages access', :project, :no_type, :not_found
|
||||
|
|
@ -235,7 +237,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
|
|||
|
||||
expect do
|
||||
get api(package_url, user)
|
||||
end.not_to exceed_query_limit(control)
|
||||
end.not_to exceed_query_limit(control).with_threshold(4)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -286,7 +288,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
|
|||
end
|
||||
|
||||
context 'project is private' do
|
||||
let(:project) { create(:project, :private) }
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
|
||||
it 'returns 404 for non authenticated user' do
|
||||
get api(package_url)
|
||||
|
|
@ -362,6 +364,235 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET /projects/:id/packages/:package_id/pipelines' do
|
||||
let(:package_pipelines_url) { "/projects/#{project.id}/packages/#{package1.id}/pipelines" }
|
||||
|
||||
let(:tokens) do
|
||||
{
|
||||
personal_access_token: personal_access_token.token,
|
||||
job_token: job.token
|
||||
}
|
||||
end
|
||||
|
||||
let_it_be(:personal_access_token) { create(:personal_access_token) }
|
||||
let_it_be(:user) { personal_access_token.user }
|
||||
let_it_be(:job) { create(:ci_build, :running, user: user, project: project) }
|
||||
let(:headers) { {} }
|
||||
|
||||
subject { get api(package_pipelines_url) }
|
||||
|
||||
shared_examples 'returns package pipelines' do |expected_status|
|
||||
it 'returns the first page of package pipelines' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(expected_status)
|
||||
expect(response).to match_response_schema('public_api/v4/packages/pipelines')
|
||||
expect(json_response.length).to eq(3)
|
||||
expect(json_response.pluck('id')).to eq(pipelines.reverse.map(&:id))
|
||||
end
|
||||
end
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'when the package does not exist' do
|
||||
let(:package_pipelines_url) { "/projects/#{project.id}/packages/0/pipelines" }
|
||||
|
||||
it_behaves_like 'returning response status', :not_found
|
||||
end
|
||||
|
||||
context 'when there are no pipelines for the package' do
|
||||
let(:package_pipelines_url) { "/projects/#{project.id}/packages/#{package2.id}/pipelines" }
|
||||
|
||||
it 'returns an empty response' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(response).to match_response_schema('public_api/v4/packages/pipelines')
|
||||
expect(json_response.length).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid package and pipelines' do
|
||||
let!(:pipelines) do
|
||||
create_list(:ci_pipeline, 3, user: user, project: project).each do |pipeline|
|
||||
create(:package_build_info, package: package1, pipeline: pipeline)
|
||||
end
|
||||
end
|
||||
|
||||
where(:visibility, :user_role, :member, :token_type, :valid_token, :shared_examples_name, :expected_status) do
|
||||
:public | :developer | true | :personal_access_token | true | 'returns package pipelines' | :success
|
||||
:public | :guest | true | :personal_access_token | true | 'returns package pipelines' | :success
|
||||
:public | :developer | true | :personal_access_token | false | 'returning response status' | :unauthorized
|
||||
:public | :guest | true | :personal_access_token | false | 'returning response status' | :unauthorized
|
||||
:public | :developer | false | :personal_access_token | true | 'returns package pipelines' | :success
|
||||
:public | :guest | false | :personal_access_token | true | 'returns package pipelines' | :success
|
||||
:public | :developer | false | :personal_access_token | false | 'returning response status' | :unauthorized
|
||||
:public | :guest | false | :personal_access_token | false | 'returning response status' | :unauthorized
|
||||
:public | :anonymous | false | nil | true | 'returns package pipelines' | :success
|
||||
:private | :developer | true | :personal_access_token | true | 'returns package pipelines' | :success
|
||||
:private | :guest | true | :personal_access_token | true | 'returning response status' | :forbidden
|
||||
:private | :developer | true | :personal_access_token | false | 'returning response status' | :unauthorized
|
||||
:private | :guest | true | :personal_access_token | false | 'returning response status' | :unauthorized
|
||||
:private | :developer | false | :personal_access_token | true | 'returning response status' | :not_found
|
||||
:private | :guest | false | :personal_access_token | true | 'returning response status' | :not_found
|
||||
:private | :developer | false | :personal_access_token | false | 'returning response status' | :unauthorized
|
||||
:private | :guest | false | :personal_access_token | false | 'returning response status' | :unauthorized
|
||||
:private | :anonymous | false | nil | true | 'returning response status' | :not_found
|
||||
:public | :developer | true | :job_token | true | 'returns package pipelines' | :success
|
||||
:public | :guest | true | :job_token | true | 'returns package pipelines' | :success
|
||||
:public | :developer | true | :job_token | false | 'returning response status' | :unauthorized
|
||||
:public | :guest | true | :job_token | false | 'returning response status' | :unauthorized
|
||||
:public | :developer | false | :job_token | true | 'returns package pipelines' | :success
|
||||
:public | :guest | false | :job_token | true | 'returns package pipelines' | :success
|
||||
:public | :developer | false | :job_token | false | 'returning response status' | :unauthorized
|
||||
:public | :guest | false | :job_token | false | 'returning response status' | :unauthorized
|
||||
:private | :developer | true | :job_token | true | 'returns package pipelines' | :success
|
||||
# TODO uncomment the spec below when https://gitlab.com/gitlab-org/gitlab/-/issues/370998 is resolved
|
||||
# :private | :guest | true | :job_token | true | 'returning response status' | :forbidden
|
||||
:private | :developer | true | :job_token | false | 'returning response status' | :unauthorized
|
||||
:private | :guest | true | :job_token | false | 'returning response status' | :unauthorized
|
||||
:private | :developer | false | :job_token | true | 'returning response status' | :not_found
|
||||
:private | :guest | false | :job_token | true | 'returning response status' | :not_found
|
||||
:private | :developer | false | :job_token | false | 'returning response status' | :unauthorized
|
||||
:private | :guest | false | :job_token | false | 'returning response status' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
subject { get api(package_pipelines_url), headers: headers }
|
||||
|
||||
let(:invalid_token) { 'invalid-token123' }
|
||||
let(:token) { valid_token ? tokens[token_type] : invalid_token }
|
||||
let(:headers) do
|
||||
case token_type
|
||||
when :personal_access_token
|
||||
{ Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER => token }
|
||||
when :job_token
|
||||
{ Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER => token }
|
||||
when nil
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
project.update!(visibility: visibility.to_s)
|
||||
project.send("add_#{user_role}", user) if member && user_role != :anonymous
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:expected_status]
|
||||
end
|
||||
end
|
||||
|
||||
context 'pagination' do
|
||||
shared_context 'setup pipeline records' do
|
||||
let!(:pipelines) do
|
||||
create_list(:package_build_info, 21, :with_pipeline, package: package1)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'returns the default number of pipelines' do
|
||||
it do
|
||||
subject
|
||||
|
||||
expect(json_response.size).to eq(20)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'returns an error about the invalid per_page value' do
|
||||
it do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to match(/per_page does not have a valid value/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without pagination params' do
|
||||
include_context 'setup pipeline records'
|
||||
|
||||
it_behaves_like 'returns the default number of pipelines'
|
||||
end
|
||||
|
||||
context 'with valid per_page value' do
|
||||
let(:per_page) { 11 }
|
||||
|
||||
subject { get api(package_pipelines_url, user), params: { per_page: per_page } }
|
||||
|
||||
include_context 'setup pipeline records'
|
||||
|
||||
it 'returns the correct number of pipelines' do
|
||||
subject
|
||||
|
||||
expect(json_response.size).to eq(per_page)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid pagination params' do
|
||||
subject { get api(package_pipelines_url, user), params: { per_page: per_page } }
|
||||
|
||||
context 'with non-positive per_page' do
|
||||
let(:per_page) { -2 }
|
||||
|
||||
it_behaves_like 'returns an error about the invalid per_page value'
|
||||
end
|
||||
|
||||
context 'with a too high value for per_page' do
|
||||
let(:per_page) { 21 }
|
||||
|
||||
it_behaves_like 'returns an error about the invalid per_page value'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid pagination params' do
|
||||
let_it_be(:package1) { create(:npm_package, :last_downloaded_at, project: project) }
|
||||
let_it_be(:build_info1) { create(:package_build_info, :with_pipeline, package: package1) }
|
||||
let_it_be(:build_info2) { create(:package_build_info, :with_pipeline, package: package1) }
|
||||
let_it_be(:build_info3) { create(:package_build_info, :with_pipeline, package: package1) }
|
||||
|
||||
let(:pipeline1) { build_info1.pipeline }
|
||||
let(:pipeline2) { build_info2.pipeline }
|
||||
let(:pipeline3) { build_info3.pipeline }
|
||||
|
||||
let(:per_page) { 2 }
|
||||
|
||||
context 'with no cursor supplied' do
|
||||
subject { get api(package_pipelines_url, user), params: { per_page: per_page } }
|
||||
|
||||
it 'returns first 2 pipelines' do
|
||||
subject
|
||||
|
||||
expect(json_response.pluck('id')).to contain_exactly(pipeline3.id, pipeline2.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a cursor parameter' do
|
||||
let(:cursor) { Base64.urlsafe_encode64(Gitlab::Json.dump(cursor_attributes)) }
|
||||
|
||||
subject { get api(package_pipelines_url, user), params: { per_page: per_page, cursor: cursor } }
|
||||
|
||||
before do
|
||||
subject
|
||||
end
|
||||
|
||||
context 'with a cursor for the next page' do
|
||||
let(:cursor_attributes) { { "id" => build_info2.id, "_kd" => "n" } }
|
||||
|
||||
it 'returns the next page of records' do
|
||||
expect(json_response.pluck('id')).to contain_exactly(pipeline1.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a cursor for the previous page' do
|
||||
let(:cursor_attributes) { { "id" => build_info1.id, "_kd" => "p" } }
|
||||
|
||||
it 'returns the previous page of records' do
|
||||
expect(json_response.pluck('id')).to contain_exactly(pipeline3.id, pipeline2.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /projects/:id/packages/:package_id' do
|
||||
context 'without the need for a license' do
|
||||
context 'project is public' do
|
||||
|
|
@ -379,7 +610,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
|
|||
end
|
||||
|
||||
context 'project is private' do
|
||||
let(:project) { create(:project, :private) }
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
|
||||
before do
|
||||
expect(::Packages::Maven::Metadata::SyncWorker).not_to receive(:perform_async)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,195 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Users::SetNamespaceCommitEmailService, feature_category: :user_profile do
|
||||
include AfterNextHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:email) { create(:email, user: user) }
|
||||
let_it_be(:existing_achievement) { create(:achievement, namespace: group) }
|
||||
|
||||
let(:namespace) { group }
|
||||
let(:current_user) { user }
|
||||
let(:target_user) { user }
|
||||
let(:email_id) { email.id }
|
||||
let(:params) { { user: target_user } }
|
||||
let(:service) { described_class.new(current_user, namespace, email_id, params) }
|
||||
|
||||
before_all do
|
||||
group.add_reporter(user)
|
||||
end
|
||||
|
||||
shared_examples 'success' do
|
||||
it 'creates namespace commit email' do
|
||||
result = service.execute
|
||||
|
||||
expect(result.payload[:namespace_commit_email]).to be_a(Users::NamespaceCommitEmail)
|
||||
expect(result.payload[:namespace_commit_email]).to be_persisted
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
context 'when current_user is not provided' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it 'returns error message' do
|
||||
expect(service.execute.message)
|
||||
.to eq("User doesn't exist or you don't have permission to change namespace commit emails.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when current_user does not have permission to change namespace commit emails' do
|
||||
let(:target_user) { create(:user) }
|
||||
|
||||
it 'returns error message' do
|
||||
expect(service.execute.message)
|
||||
.to eq("User doesn't exist or you don't have permission to change namespace commit emails.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when target_user does not have permission to access the namespace' do
|
||||
let(:namespace) { create(:group) }
|
||||
|
||||
it 'returns error message' do
|
||||
expect(service.execute.message).to eq("Namespace doesn't exist or you don't have permission.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when namespace is not provided' do
|
||||
let(:namespace) { nil }
|
||||
|
||||
it 'returns error message' do
|
||||
expect(service.execute.message).to eq('Namespace must be provided.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when target user is not current user' do
|
||||
context 'when current user is an admin' do
|
||||
let(:current_user) { create(:user, :admin) }
|
||||
|
||||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it 'creates namespace commit email' do
|
||||
result = service.execute
|
||||
|
||||
expect(result.payload[:namespace_commit_email]).to be_a(Users::NamespaceCommitEmail)
|
||||
expect(result.payload[:namespace_commit_email]).to be_persisted
|
||||
end
|
||||
end
|
||||
|
||||
context 'when admin mode is not enabled' do
|
||||
it 'returns error message' do
|
||||
expect(service.execute.message)
|
||||
.to eq("User doesn't exist or you don't have permission to change namespace commit emails.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when current user is not an admin' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
it 'returns error message' do
|
||||
expect(service.execute.message)
|
||||
.to eq("User doesn't exist or you don't have permission to change namespace commit emails.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when namespace commit email does not exist' do
|
||||
context 'when email_id is not provided' do
|
||||
let(:email_id) { nil }
|
||||
|
||||
it 'returns error message' do
|
||||
expect(service.execute.message).to eq('Email must be provided.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when model save fails' do
|
||||
before do
|
||||
allow_next(::Users::NamespaceCommitEmail).to receive(:save).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns error message' do
|
||||
expect(service.execute.message).to eq('Failed to save namespace commit email.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when namepsace is a group' do
|
||||
it_behaves_like 'success'
|
||||
end
|
||||
|
||||
context 'when namespace is a user' do
|
||||
let(:namespace) { current_user.namespace }
|
||||
|
||||
it_behaves_like 'success'
|
||||
end
|
||||
|
||||
context 'when namespace is a project' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:namespace) { project.project_namespace }
|
||||
|
||||
before do
|
||||
project.add_reporter(current_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'success'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when namespace commit email already exists' do
|
||||
let!(:existing_namespace_commit_email) do
|
||||
create(:namespace_commit_email,
|
||||
user: target_user,
|
||||
namespace: namespace,
|
||||
email: create(:email, user: target_user))
|
||||
end
|
||||
|
||||
context 'when email_id is not provided' do
|
||||
let(:email_id) { nil }
|
||||
|
||||
it 'destroys the namespace commit email' do
|
||||
result = service.execute
|
||||
|
||||
expect(result.message).to be_nil
|
||||
expect(result.payload[:namespace_commit_email]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'and email_id is provided' do
|
||||
let(:email_id) { create(:email, user: current_user).id }
|
||||
|
||||
it 'updates namespace commit email' do
|
||||
result = service.execute
|
||||
|
||||
existing_namespace_commit_email.reload
|
||||
|
||||
expect(result.payload[:namespace_commit_email]).to eq(existing_namespace_commit_email)
|
||||
expect(existing_namespace_commit_email.email_id).to eq(email_id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when model save fails' do
|
||||
before do
|
||||
allow_any_instance_of(::Users::NamespaceCommitEmail).to receive(:save).and_return(false) # rubocop:disable RSpec/AnyInstanceOf
|
||||
end
|
||||
|
||||
it 'returns generic error message' do
|
||||
expect(service.execute.message).to eq('Failed to save namespace commit email.')
|
||||
end
|
||||
|
||||
context 'with model errors' do
|
||||
before do
|
||||
allow_any_instance_of(::Users::NamespaceCommitEmail).to receive_message_chain(:errors, :empty?).and_return(false) # rubocop:disable RSpec/AnyInstanceOf
|
||||
allow_any_instance_of(::Users::NamespaceCommitEmail).to receive_message_chain(:errors, :full_messages, :to_sentence).and_return('Model error') # rubocop:disable RSpec/AnyInstanceOf
|
||||
end
|
||||
|
||||
it 'returns the model error message' do
|
||||
expect(service.execute.message).to eq('Model error')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -80,10 +80,13 @@ RSpec.shared_examples 'work items comments' do |type|
|
|||
it 'shows work item note actions' do
|
||||
set_comment
|
||||
|
||||
click_button "Comment"
|
||||
|
||||
send_keys([modifier_key, :enter])
|
||||
wait_for_requests
|
||||
|
||||
page.within(".main-notes-list") do
|
||||
expect(page).to have_content comment
|
||||
end
|
||||
|
||||
page.within('.timeline-entry.note.note-wrapper.note-comment:last-child') do
|
||||
expect(page).to have_selector('[data-testid="work-item-note-actions"]')
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue