Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d17c58402b
commit
b81fd57f3d
|
@ -1546,9 +1546,6 @@ Gitlab/NamespacedClass:
|
|||
- 'app/finders/context_commits_finder.rb'
|
||||
- 'app/finders/contributed_projects_finder.rb'
|
||||
- 'app/finders/deployments_finder.rb'
|
||||
- 'app/finders/environment_names_finder.rb'
|
||||
- 'app/finders/environments_finder.rb'
|
||||
- 'app/finders/environments_by_deployments_finder.rb'
|
||||
- 'app/finders/events_finder.rb'
|
||||
- 'app/finders/feature_flags_finder.rb'
|
||||
- 'app/finders/feature_flags_user_lists_finder.rb'
|
||||
|
|
|
@ -1 +1 @@
|
|||
d0d2f4a763c2be059e4e2353f2e5affaff83305d
|
||||
a7bc2f86b507daaaf9f18e0ea189b062d6149720
|
||||
|
|
|
@ -197,7 +197,7 @@ class GroupsController < Groups::ApplicationController
|
|||
def unfoldered_environment_names
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: EnvironmentNamesFinder.new(@group, current_user).execute
|
||||
render json: Environments::EnvironmentNamesFinder.new(@group, current_user).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,8 +26,7 @@ class InvitesController < ApplicationController
|
|||
experiment('members/invite_email', actor: member).track(:accepted) if initial_invite_email?
|
||||
session.delete(:invite_type)
|
||||
|
||||
redirect_to invite_details[:path], notice: _("You have been granted %{member_human_access} access to %{title} %{name}.") %
|
||||
{ member_human_access: member.human_access, title: invite_details[:title], name: invite_details[:name] }
|
||||
redirect_to invite_details[:path], notice: helpers.invite_accepted_notice(member)
|
||||
else
|
||||
redirect_back_or_default(options: { alert: _("The invitation could not be accepted.") })
|
||||
end
|
||||
|
@ -91,7 +90,7 @@ class InvitesController < ApplicationController
|
|||
def authenticate_user!
|
||||
return if current_user
|
||||
|
||||
store_location_for :user, request.fullpath
|
||||
store_location_for(:user, invite_landing_url) if member
|
||||
|
||||
if user_sign_up?
|
||||
redirect_to new_user_registration_path(invite_email: member.invite_email), notice: _("To accept this invitation, create an account or sign in.")
|
||||
|
@ -116,6 +115,10 @@ class InvitesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def invite_landing_url
|
||||
root_url + invite_details[:path]
|
||||
end
|
||||
|
||||
def invite_details
|
||||
@invite_details ||= case member.source
|
||||
when Project
|
||||
|
@ -123,14 +126,14 @@ class InvitesController < ApplicationController
|
|||
name: member.source.full_name,
|
||||
url: project_url(member.source),
|
||||
title: _("project"),
|
||||
path: project_path(member.source)
|
||||
path: activity_project_path(member.source)
|
||||
}
|
||||
when Group
|
||||
{
|
||||
name: member.source.name,
|
||||
url: group_url(member.source),
|
||||
title: _("group"),
|
||||
path: group_path(member.source)
|
||||
path: activity_group_path(member.source)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ class Projects::BlameController < Projects::ApplicationController
|
|||
|
||||
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
|
||||
environment_params[:find_latest] = true
|
||||
@environment = EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
|
||||
@environment = Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
|
||||
|
||||
@blame = Gitlab::Blame.new(@blob, @commit)
|
||||
@blame = Gitlab::View::Presenter::Factory.new(@blame, project: @project, path: @path).fabricate!
|
||||
|
|
|
@ -214,7 +214,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
def show_html
|
||||
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
|
||||
environment_params[:find_latest] = true
|
||||
@environment = EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
|
||||
@environment = Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
|
||||
@last_commit = @repository.last_commit_for_path(@commit.id, @blob.path, literal_pathspec: true)
|
||||
@code_navigation_path = Gitlab::CodeNavigationPath.new(@project, @blob.commit_id).full_json_path_for(@blob.path)
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
@diffs = commit.diffs(opts)
|
||||
@notes_count = commit.notes.count
|
||||
|
||||
@environment = EnvironmentsByDeploymentsFinder.new(@project, current_user, commit: @commit, find_latest: true).execute.last
|
||||
@environment = Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, commit: @commit, find_latest: true).execute.last
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
|
|
|
@ -136,7 +136,7 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
if compare
|
||||
environment_params = source_project.repository.branch_exists?(head_ref) ? { ref: head_ref } : { commit: compare.commit }
|
||||
environment_params[:find_latest] = true
|
||||
@environment = EnvironmentsByDeploymentsFinder.new(source_project, current_user, environment_params).execute.last
|
||||
@environment = Environments::EnvironmentsByDeploymentsFinder.new(source_project, current_user, environment_params).execute.last
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ module Projects
|
|||
def environment
|
||||
strong_memoize(:environment) do
|
||||
if cluster_params.key?(:environment_name)
|
||||
EnvironmentsFinder.new(project, current_user, name: cluster_params[:environment_name]).execute.first
|
||||
::Environments::EnvironmentsFinder.new(project, current_user, name: cluster_params[:environment_name]).execute.first
|
||||
else
|
||||
project.default_environment
|
||||
end
|
||||
|
|
|
@ -311,7 +311,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
def unfoldered_environment_names
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: EnvironmentNamesFinder.new(@project, current_user).execute
|
||||
render json: Environments::EnvironmentNamesFinder.new(@project, current_user).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,7 +18,11 @@ module Registrations
|
|||
if result[:status] == :success
|
||||
return redirect_to new_users_sign_up_group_path if show_signup_onboarding?
|
||||
|
||||
redirect_to path_for_signed_in_user(current_user)
|
||||
if current_user.members.count == 1
|
||||
redirect_to path_for_signed_in_user(current_user), notice: helpers.invite_accepted_notice(current_user.members.last)
|
||||
else
|
||||
redirect_to path_for_signed_in_user(current_user)
|
||||
end
|
||||
else
|
||||
render :show
|
||||
end
|
||||
|
@ -48,7 +52,20 @@ module Registrations
|
|||
def path_for_signed_in_user(user)
|
||||
return users_almost_there_path if requires_confirmation?(user)
|
||||
|
||||
stored_location_for(user) || dashboard_projects_path
|
||||
stored_location_for(user) || members_activity_path(user)
|
||||
end
|
||||
|
||||
def members_activity_path(user)
|
||||
return dashboard_projects_path unless user.members.count >= 1
|
||||
|
||||
case user.members.last.source
|
||||
when Project
|
||||
activity_project_path(user.members.last.source)
|
||||
when Group
|
||||
activity_group_path(user.members.last.source)
|
||||
else
|
||||
dashboard_projects_path
|
||||
end
|
||||
end
|
||||
|
||||
def show_signup_onboarding?
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Finder for obtaining the unique environment names of a project or group.
|
||||
#
|
||||
# This finder exists so that the merge requests "environments" filter can be
|
||||
# populated with a unique list of environment names. If we retrieve _just_ the
|
||||
# environments, duplicates may be present (e.g. multiple projects in a group
|
||||
# having a "staging" environment).
|
||||
#
|
||||
# In addition, this finder only produces unfoldered environments. We do this
|
||||
# because when searching for environments we want to exclude review app
|
||||
# environments.
|
||||
class EnvironmentNamesFinder
|
||||
attr_reader :project_or_group, :current_user
|
||||
|
||||
def initialize(project_or_group, current_user = nil)
|
||||
@project_or_group = project_or_group
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def execute
|
||||
all_environments.unfoldered.order_by_name.pluck_unique_names
|
||||
end
|
||||
|
||||
def all_environments
|
||||
if project_or_group.is_a?(Namespace)
|
||||
namespace_environments
|
||||
else
|
||||
project_environments
|
||||
end
|
||||
end
|
||||
|
||||
def namespace_environments
|
||||
# We assume reporter access is needed for the :read_environment permission
|
||||
# here. This expection is also present in
|
||||
# IssuableFinder::Params#min_access_level, which is used for filtering out
|
||||
# merge requests that don't have the right permissions.
|
||||
#
|
||||
# We use this approach so we don't need to load every project into memory
|
||||
# just to verify if we can see their environments. Doing so would not be
|
||||
# efficient, and possibly mess up pagination if certain projects are not
|
||||
# meant to be visible.
|
||||
projects = project_or_group
|
||||
.all_projects
|
||||
.public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
|
||||
|
||||
Environment.for_project(projects)
|
||||
end
|
||||
|
||||
def project_environments
|
||||
if Ability.allowed?(current_user, :read_environment, project_or_group)
|
||||
project_or_group.environments
|
||||
else
|
||||
Environment.none
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Environments
|
||||
# Finder for obtaining the unique environment names of a project or group.
|
||||
#
|
||||
# This finder exists so that the merge requests "environments" filter can be
|
||||
# populated with a unique list of environment names. If we retrieve _just_ the
|
||||
# environments, duplicates may be present (e.g. multiple projects in a group
|
||||
# having a "staging" environment).
|
||||
#
|
||||
# In addition, this finder only produces unfoldered environments. We do this
|
||||
# because when searching for environments we want to exclude review app
|
||||
# environments.
|
||||
class EnvironmentNamesFinder
|
||||
attr_reader :project_or_group, :current_user
|
||||
|
||||
def initialize(project_or_group, current_user = nil)
|
||||
@project_or_group = project_or_group
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def execute
|
||||
all_environments.unfoldered.order_by_name.pluck_unique_names
|
||||
end
|
||||
|
||||
def all_environments
|
||||
if project_or_group.is_a?(Namespace)
|
||||
namespace_environments
|
||||
else
|
||||
project_environments
|
||||
end
|
||||
end
|
||||
|
||||
def namespace_environments
|
||||
# We assume reporter access is needed for the :read_environment permission
|
||||
# here. This expection is also present in
|
||||
# IssuableFinder::Params#min_access_level, which is used for filtering out
|
||||
# merge requests that don't have the right permissions.
|
||||
#
|
||||
# We use this approach so we don't need to load every project into memory
|
||||
# just to verify if we can see their environments. Doing so would not be
|
||||
# efficient, and possibly mess up pagination if certain projects are not
|
||||
# meant to be visible.
|
||||
projects = project_or_group
|
||||
.all_projects
|
||||
.public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
|
||||
|
||||
Environment.for_project(projects)
|
||||
end
|
||||
|
||||
def project_environments
|
||||
if Ability.allowed?(current_user, :read_environment, project_or_group)
|
||||
project_or_group.environments
|
||||
else
|
||||
Environment.none
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Environments
|
||||
class EnvironmentsByDeploymentsFinder
|
||||
attr_reader :project, :current_user, :params
|
||||
|
||||
def initialize(project, current_user, params = {})
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def execute
|
||||
deployments = project.deployments
|
||||
deployments =
|
||||
if ref
|
||||
deployments_query = params[:with_tags] ? 'ref = :ref OR tag IS TRUE' : 'ref = :ref'
|
||||
deployments.where(deployments_query, ref: ref.to_s)
|
||||
elsif commit
|
||||
deployments.where(sha: commit.sha)
|
||||
else
|
||||
deployments.none
|
||||
end
|
||||
|
||||
environment_ids = deployments
|
||||
.group(:environment_id)
|
||||
.select(:environment_id)
|
||||
|
||||
environments = project.environments.available
|
||||
.where(id: environment_ids)
|
||||
|
||||
if params[:find_latest]
|
||||
find_one(environments.order_by_last_deployed_at_desc)
|
||||
else
|
||||
find_all(environments.order_by_last_deployed_at.to_a)
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
private
|
||||
|
||||
def find_one(environments)
|
||||
[environments.find { |environment| valid_environment?(environment) }].compact
|
||||
end
|
||||
|
||||
def find_all(environments)
|
||||
environments.select { |environment| valid_environment?(environment) }
|
||||
end
|
||||
|
||||
def valid_environment?(environment)
|
||||
# Go in order of cost: SQL calls are cheaper than Gitaly calls
|
||||
return false unless Ability.allowed?(current_user, :read_environment, environment)
|
||||
|
||||
return false if ref && params[:recently_updated] && !environment.recently_updated_on_branch?(ref)
|
||||
return false if ref && commit && !environment.includes_commit?(commit)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def ref
|
||||
params[:ref].try(:to_s)
|
||||
end
|
||||
|
||||
def commit
|
||||
params[:commit]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,65 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Environments
|
||||
class EnvironmentsFinder
|
||||
attr_reader :project, :current_user, :params
|
||||
|
||||
InvalidStatesError = Class.new(StandardError)
|
||||
|
||||
def initialize(project, current_user, params = {})
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
environments = project.environments
|
||||
environments = by_name(environments)
|
||||
environments = by_search(environments)
|
||||
|
||||
# Raises InvalidStatesError if params[:states] contains invalid states.
|
||||
by_states(environments)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def by_name(environments)
|
||||
if params[:name].present?
|
||||
environments.for_name(params[:name])
|
||||
else
|
||||
environments
|
||||
end
|
||||
end
|
||||
|
||||
def by_search(environments)
|
||||
if params[:search].present?
|
||||
environments.for_name_like(params[:search], limit: nil)
|
||||
else
|
||||
environments
|
||||
end
|
||||
end
|
||||
|
||||
def by_states(environments)
|
||||
if params[:states].present?
|
||||
environments_with_states(environments)
|
||||
else
|
||||
environments
|
||||
end
|
||||
end
|
||||
|
||||
def environments_with_states(environments)
|
||||
# Convert to array of strings
|
||||
states = Array(params[:states]).map(&:to_s)
|
||||
|
||||
raise InvalidStatesError, _('Requested states are invalid') unless valid_states?(states)
|
||||
|
||||
environments.with_states(states)
|
||||
end
|
||||
|
||||
def valid_states?(states)
|
||||
valid_states = Environment.valid_states.map(&:to_s)
|
||||
|
||||
(states - valid_states).empty?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,67 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class EnvironmentsByDeploymentsFinder
|
||||
attr_reader :project, :current_user, :params
|
||||
|
||||
def initialize(project, current_user, params = {})
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def execute
|
||||
deployments = project.deployments
|
||||
deployments =
|
||||
if ref
|
||||
deployments_query = params[:with_tags] ? 'ref = :ref OR tag IS TRUE' : 'ref = :ref'
|
||||
deployments.where(deployments_query, ref: ref.to_s)
|
||||
elsif commit
|
||||
deployments.where(sha: commit.sha)
|
||||
else
|
||||
deployments.none
|
||||
end
|
||||
|
||||
environment_ids = deployments
|
||||
.group(:environment_id)
|
||||
.select(:environment_id)
|
||||
|
||||
environments = project.environments.available
|
||||
.where(id: environment_ids)
|
||||
|
||||
if params[:find_latest]
|
||||
find_one(environments.order_by_last_deployed_at_desc)
|
||||
else
|
||||
find_all(environments.order_by_last_deployed_at.to_a)
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
private
|
||||
|
||||
def find_one(environments)
|
||||
[environments.find { |environment| valid_environment?(environment) }].compact
|
||||
end
|
||||
|
||||
def find_all(environments)
|
||||
environments.select { |environment| valid_environment?(environment) }
|
||||
end
|
||||
|
||||
def valid_environment?(environment)
|
||||
# Go in order of cost: SQL calls are cheaper than Gitaly calls
|
||||
return false unless Ability.allowed?(current_user, :read_environment, environment)
|
||||
|
||||
return false if ref && params[:recently_updated] && !environment.recently_updated_on_branch?(ref)
|
||||
return false if ref && commit && !environment.includes_commit?(commit)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def ref
|
||||
params[:ref].try(:to_s)
|
||||
end
|
||||
|
||||
def commit
|
||||
params[:commit]
|
||||
end
|
||||
end
|
|
@ -1,63 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class EnvironmentsFinder
|
||||
attr_reader :project, :current_user, :params
|
||||
|
||||
InvalidStatesError = Class.new(StandardError)
|
||||
|
||||
def initialize(project, current_user, params = {})
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
environments = project.environments
|
||||
environments = by_name(environments)
|
||||
environments = by_search(environments)
|
||||
|
||||
# Raises InvalidStatesError if params[:states] contains invalid states.
|
||||
by_states(environments)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def by_name(environments)
|
||||
if params[:name].present?
|
||||
environments.for_name(params[:name])
|
||||
else
|
||||
environments
|
||||
end
|
||||
end
|
||||
|
||||
def by_search(environments)
|
||||
if params[:search].present?
|
||||
environments.for_name_like(params[:search], limit: nil)
|
||||
else
|
||||
environments
|
||||
end
|
||||
end
|
||||
|
||||
def by_states(environments)
|
||||
if params[:states].present?
|
||||
environments_with_states(environments)
|
||||
else
|
||||
environments
|
||||
end
|
||||
end
|
||||
|
||||
def environments_with_states(environments)
|
||||
# Convert to array of strings
|
||||
states = Array(params[:states]).map(&:to_s)
|
||||
|
||||
raise InvalidStatesError, _('Requested states are invalid') unless valid_states?(states)
|
||||
|
||||
environments.with_states(states)
|
||||
end
|
||||
|
||||
def valid_states?(states)
|
||||
valid_states = Environment.valid_states.map(&:to_s)
|
||||
|
||||
(states - valid_states).empty?
|
||||
end
|
||||
end
|
|
@ -21,8 +21,8 @@ module Resolvers
|
|||
def resolve(**args)
|
||||
return unless project.present?
|
||||
|
||||
EnvironmentsFinder.new(project, context[:current_user], args).execute
|
||||
rescue EnvironmentsFinder::InvalidStatesError => exception
|
||||
Environments::EnvironmentsFinder.new(project, context[:current_user], args).execute
|
||||
rescue Environments::EnvironmentsFinder::InvalidStatesError => exception
|
||||
raise Gitlab::Graphql::Errors::ArgumentError, exception.message
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,6 +46,17 @@ module InviteMembersHelper
|
|||
end
|
||||
end
|
||||
|
||||
def invite_accepted_notice(member)
|
||||
case member.source
|
||||
when Project
|
||||
_("You have been granted %{member_human_access} access to project %{name}.") %
|
||||
{ member_human_access: member.human_access, name: member.source.name }
|
||||
when Group
|
||||
_("You have been granted %{member_human_access} access to group %{name}.") %
|
||||
{ member_human_access: member.human_access, name: member.source.name }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def invite_members_url(form_model)
|
||||
|
|
|
@ -106,6 +106,14 @@ class BroadcastMessage < ApplicationRecord
|
|||
return false if current_path.blank? && target_path.present?
|
||||
return true if current_path.blank? || target_path.blank?
|
||||
|
||||
# Ensure paths are consistent across callers.
|
||||
# This fixes a mismatch between requests in the GUI and CLI
|
||||
#
|
||||
# This has to be reassigned due to frozen strings being provided.
|
||||
unless current_path.start_with?("/")
|
||||
current_path = "/#{current_path}"
|
||||
end
|
||||
|
||||
escaped = Regexp.escape(target_path).gsub('\\*', '.*')
|
||||
regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
|
||||
|
||||
|
|
|
@ -1367,11 +1367,11 @@ class MergeRequest < ApplicationRecord
|
|||
def environments_for(current_user, latest: false)
|
||||
return [] unless diff_head_commit
|
||||
|
||||
envs = EnvironmentsByDeploymentsFinder.new(target_project, current_user,
|
||||
envs = Environments::EnvironmentsByDeploymentsFinder.new(target_project, current_user,
|
||||
ref: target_branch, commit: diff_head_commit, with_tags: true, find_latest: latest).execute
|
||||
|
||||
if source_project
|
||||
envs.concat EnvironmentsByDeploymentsFinder.new(source_project, current_user,
|
||||
envs.concat Environments::EnvironmentsByDeploymentsFinder.new(source_project, current_user,
|
||||
ref: source_branch, commit: diff_head_commit, find_latest: latest).execute
|
||||
end
|
||||
|
||||
|
|
|
@ -241,7 +241,6 @@ class Service < ApplicationRecord
|
|||
service.project_id = project_id
|
||||
service.group_id = group_id
|
||||
service.inherit_from_id = integration.id if integration.instance? || integration.group
|
||||
service.active = false if service.invalid?
|
||||
service
|
||||
end
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ module Ci
|
|||
private
|
||||
|
||||
def environments
|
||||
@environments ||= EnvironmentsByDeploymentsFinder
|
||||
@environments ||= Environments::EnvironmentsByDeploymentsFinder
|
||||
.new(project, current_user, ref: @ref, recently_updated: true)
|
||||
.execute
|
||||
end
|
||||
|
|
|
@ -17,11 +17,16 @@ module Lfs
|
|||
|
||||
success
|
||||
rescue => err
|
||||
Gitlab::ErrorTracking.log_exception(err, extra_context)
|
||||
error(err.message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extra_context
|
||||
{ project_id: project.id, user_id: current_user&.id }.compact
|
||||
end
|
||||
|
||||
# Currently we only set repository_type for design repository objects, so
|
||||
# push mirroring must send objects with a `nil` repository type - but if the
|
||||
# wiki repository uses LFS, its objects will also be sent. This will be
|
||||
|
|
|
@ -84,7 +84,7 @@ module Prometheus
|
|||
|
||||
def environment
|
||||
strong_memoize(:environment) do
|
||||
EnvironmentsFinder.new(project, nil, name: 'production').execute.first ||
|
||||
Environments::EnvironmentsFinder.new(project, nil, name: 'production').execute.first ||
|
||||
project.environments.first
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,7 +34,6 @@ class BuildFinishedWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
|
||||
# We execute these async as these are independent operations.
|
||||
BuildHooksWorker.perform_async(build.id)
|
||||
ExpirePipelineCacheWorker.perform_async(build.pipeline_id)
|
||||
ChatNotificationWorker.perform_async(build.id) if build.pipeline.chat?
|
||||
|
||||
if build.failed?
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Redirect to activity page when accepting invitation
|
||||
merge_request: 56695
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove unnecessary validation avoiding N+1 queries when building integrations
|
||||
merge_request: 59635
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Deprecate Alerts service metric
|
||||
merge_request: 59899
|
||||
author:
|
||||
type: deprecated
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix for shell announcement banners
|
||||
merge_request: 59482
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update Metrics Definitions for Runner
|
||||
merge_request: 59824
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Log exceptions in Lfs::PushService
|
||||
merge_request: 59960
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: new_graphql_keyset_pagination
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56751
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323730
|
||||
milestone: '13.10'
|
||||
type: development
|
||||
group: group::optimize
|
||||
default_enabled: false
|
|
@ -2,7 +2,7 @@
|
|||
key_path: counts.clusters_applications_runner
|
||||
description: Total GitLab Managed clusters with GitLab Managed App:Runner installed
|
||||
product_section: ops
|
||||
product_stage:
|
||||
product_stage: configure
|
||||
product_group: group::configure
|
||||
product_category: kubernetes_management
|
||||
value_type: number
|
||||
|
@ -16,4 +16,3 @@ tier:
|
|||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
skip_validation: true
|
||||
|
|
|
@ -6,7 +6,7 @@ product_stage: monitor
|
|||
product_group: group::health
|
||||
product_category: incident_management
|
||||
value_type: number
|
||||
status: data_available
|
||||
status: deprecated
|
||||
time_frame: all
|
||||
data_source: database
|
||||
distribution:
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
---
|
||||
key_path: usage_activity_by_stage.verify.clusters_applications_runner
|
||||
description: Total GitLab Managed clusters with Runner enabled
|
||||
description: Count of users creating managed clusters with Runner enabled
|
||||
product_section: ops
|
||||
product_stage: verify
|
||||
product_group: group::runner
|
||||
product_category: runner
|
||||
product_stage: configure
|
||||
product_group: group::configure
|
||||
product_category: kubernetes_management
|
||||
value_type: number
|
||||
status: data_available
|
||||
time_frame: all
|
||||
data_source:
|
||||
data_source: database
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
skip_validation: true
|
||||
- ultimate
|
|
@ -1,16 +1,18 @@
|
|||
---
|
||||
key_path: gitlab_shared_runners_enabled
|
||||
description: Whether shared runners is enabled
|
||||
product_section: growth
|
||||
product_stage: growth
|
||||
product_group: group::product intelligence
|
||||
product_category: collection
|
||||
product_section: ops
|
||||
product_stage: verify
|
||||
product_group: group::runner
|
||||
product_category: runner
|
||||
value_type: boolean
|
||||
status: data_available
|
||||
time_frame: none
|
||||
data_source:
|
||||
data_source: database
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
skip_validation: true
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
|
@ -4756,7 +4756,7 @@ Count of projects that have enabled the Alerts service
|
|||
|
||||
Group: `group::health`
|
||||
|
||||
Status: `data_available`
|
||||
Status: `deprecated`
|
||||
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
|
@ -6506,11 +6506,11 @@ Whether shared runners is enabled
|
|||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/settings/20210204124902_gitlab_shared_runners_enabled.yml)
|
||||
|
||||
Group: `group::product intelligence`
|
||||
Group: `group::runner`
|
||||
|
||||
Status: `data_available`
|
||||
|
||||
Tiers: `free`
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `gitpod_enabled`
|
||||
|
||||
|
@ -16224,11 +16224,11 @@ Tiers: `free`
|
|||
|
||||
### `usage_activity_by_stage.verify.clusters_applications_runner`
|
||||
|
||||
Total GitLab Managed clusters with Runner enabled
|
||||
Count of users creating managed clusters with Runner enabled
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216181949_clusters_applications_runner.yml)
|
||||
|
||||
Group: `group::runner`
|
||||
Group: `group::configure`
|
||||
|
||||
Status: `data_available`
|
||||
|
||||
|
|
|
@ -56,14 +56,7 @@ The instance then notifies the user.
|
|||
## Review existing GPG keys
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/282429) in GitLab 13.10.
|
||||
> - [Deployed behind a feature flag](../feature_flags.md), disabled by default.
|
||||
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/292961) in GitLab 13.11.
|
||||
> - Enabled on GitLab.com.
|
||||
> - Recommended for production use.
|
||||
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-the-gpg-keys-view).
|
||||
|
||||
WARNING:
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/292961) in GitLab 13.12.
|
||||
|
||||
You can view all existing GPG in your GitLab instance by navigating to the
|
||||
credentials inventory GPG Keys tab, as well as the following properties:
|
||||
|
@ -73,22 +66,3 @@ credentials inventory GPG Keys tab, as well as the following properties:
|
|||
- Whether the GPG key is [verified or unverified](../project/repository/gpg_signed_commits/index.md)
|
||||
|
||||
![Credentials inventory page - GPG keys](img/credentials_inventory_gpg_keys_v13_10.png)
|
||||
|
||||
### Enable or disable the GPG keys view
|
||||
|
||||
Enabling or disabling the GPG keys view is under development but ready for production use.
|
||||
It is deployed behind a feature flag that is **enabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
|
||||
can opt to disable it.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:credential_inventory_gpg_keys)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:credential_inventory_gpg_keys)
|
||||
```
|
||||
|
|
|
@ -73,18 +73,31 @@ CSV file containing details of the resources scanned.
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/235558) in GitLab 13.6.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285476) in GitLab 13.10, options to zoom in on a date range, and download the vulnerabilities chart.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285477) in GitLab 13.11, date range slider to visualise data between given dates.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285477) in GitLab 13.11, date range slider to visualize data between given dates.
|
||||
|
||||
At the project level, the Security Dashboard displays a chart with the number of vulnerabilities over time.
|
||||
Access it by navigating to **Security & Compliance > Security Dashboard**. We display historical
|
||||
data up to 365 days. The chart's data is updated daily.
|
||||
A project's Security Dashboard displays a chart with the total number of vulnerabilities
|
||||
over time. It updates daily with up to 365 days of historical data. By default,
|
||||
it shows statistics for all vulnerability severities.
|
||||
|
||||
To access the dashboard, from your project's home page go to **Security & Compliance > Security Dashboard**.
|
||||
|
||||
![Project Security Dashboard](img/project_security_dashboard_chart_v13_11.png)
|
||||
|
||||
Filter the historical data by clicking on the corresponding legend name. The image above, for example, shows
|
||||
only the graph for vulnerabilities with **high** severity.
|
||||
### Filter the vulnerabilities chart
|
||||
|
||||
To zoom in, select the left-most icon, then select the desired rangeby dragging across the chart. Select **Remove Selection** (**{redo}**) to reset to the original date range.
|
||||
To filter the chart by vulnerability severity, select the corresponding legend name.
|
||||
|
||||
In the previous example, the chart shows statistics only for vulnerabilities of medium or unknown severity.
|
||||
|
||||
### Customize vulnerabilities chart display
|
||||
|
||||
To customize the view of the vulnerability chart, you can select:
|
||||
|
||||
- A specific time frame by using the time range handles (**{scroll-handle}**).
|
||||
- A specific area of the chart by using the left-most icon (**{marquee-selection}**) then drag
|
||||
across the chart. To reset to the original range, select **Remove Selection** (**{redo}**).
|
||||
|
||||
### Download a copy of the vulnerabilities chart
|
||||
|
||||
To download an SVG image of the chart, select **Save chart to an image** (**{download}**).
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ module API
|
|||
get ':id/environments' do
|
||||
authorize! :read_environment, user_project
|
||||
|
||||
environments = ::EnvironmentsFinder.new(user_project, current_user, params).execute
|
||||
environments = ::Environments::EnvironmentsFinder.new(user_project, current_user, params).execute
|
||||
|
||||
present paginate(environments), with: Entities::Environment, current_user: current_user
|
||||
end
|
||||
|
|
|
@ -130,7 +130,7 @@ module Gitlab
|
|||
strong_memoize(:environment) do
|
||||
next unless environment_name
|
||||
|
||||
EnvironmentsFinder
|
||||
::Environments::EnvironmentsFinder
|
||||
.new(project, nil, { name: environment_name })
|
||||
.execute
|
||||
.first
|
||||
|
|
|
@ -10,6 +10,8 @@ module Gitlab
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
def ordered_items
|
||||
raise ArgumentError, 'Relation must have a primary key' unless items.primary_key.present?
|
||||
|
||||
return super unless Gitlab::Pagination::Keyset::Order.keyset_aware?(items)
|
||||
|
||||
items
|
||||
|
@ -40,6 +42,17 @@ module Gitlab
|
|||
sliced = slice_nodes(sliced, after, :after) if after.present?
|
||||
sliced
|
||||
end
|
||||
|
||||
def items
|
||||
original_items = super
|
||||
return original_items if Gitlab::Pagination::Keyset::Order.keyset_aware?(original_items) || Feature.disabled?(:new_graphql_keyset_pagination)
|
||||
|
||||
strong_memoize(:generic_keyset_pagination_items) do
|
||||
rebuilt_items_with_keyset_order, success = Gitlab::Pagination::Keyset::SimpleOrderBuilder.build(original_items)
|
||||
|
||||
success ? rebuilt_items_with_keyset_order : original_items
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -170,6 +170,8 @@ module Gitlab
|
|||
self.class.build(column_definitions.map(&:reverse))
|
||||
end
|
||||
|
||||
alias_method :to_sql, :to_s
|
||||
|
||||
private
|
||||
|
||||
# Adds extra columns to the SELECT clause
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Pagination
|
||||
module Keyset
|
||||
# This class transforms the `order()` values from an Activerecord scope into a
|
||||
# Gitlab::Pagination::Keyset::Order instance so the query later can be used in
|
||||
# keyset pagination.
|
||||
#
|
||||
# Return values:
|
||||
# [transformed_scope, true] # true indicates that the new scope was successfully built
|
||||
# [orginal_scope, false] # false indicates that the order values are not supported in this class
|
||||
class SimpleOrderBuilder
|
||||
def self.build(scope)
|
||||
new(scope: scope).build
|
||||
end
|
||||
|
||||
def initialize(scope:)
|
||||
@scope = scope
|
||||
@order_values = scope.order_values
|
||||
@model_class = scope.model
|
||||
@arel_table = @model_class.arel_table
|
||||
@primary_key = @model_class.primary_key
|
||||
end
|
||||
|
||||
def build
|
||||
order = if order_values.empty?
|
||||
primary_key_descending_order
|
||||
elsif ordered_by_primary_key?
|
||||
primary_key_order
|
||||
elsif ordered_by_other_column?
|
||||
column_with_tie_breaker_order
|
||||
elsif ordered_by_other_column_with_tie_breaker?
|
||||
tie_breaker_attribute = order_values.second
|
||||
|
||||
tie_breaker_column_order = Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
|
||||
attribute_name: model_class.primary_key,
|
||||
order_expression: tie_breaker_attribute
|
||||
)
|
||||
|
||||
column_with_tie_breaker_order(tie_breaker_column_order)
|
||||
end
|
||||
|
||||
order ? [scope.reorder!(order), true] : [scope, false] # [scope, success]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :scope, :order_values, :model_class, :arel_table, :primary_key
|
||||
|
||||
def primary_key_descending_order
|
||||
Gitlab::Pagination::Keyset::Order.build([
|
||||
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
|
||||
attribute_name: model_class.primary_key,
|
||||
order_expression: arel_table[primary_key].desc
|
||||
)
|
||||
])
|
||||
end
|
||||
|
||||
def primary_key_order
|
||||
Gitlab::Pagination::Keyset::Order.build([
|
||||
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
|
||||
attribute_name: model_class.primary_key,
|
||||
order_expression: order_values.first
|
||||
)
|
||||
])
|
||||
end
|
||||
|
||||
def column_with_tie_breaker_order(tie_breaker_column_order = default_tie_breaker_column_order)
|
||||
order_expression = order_values.first
|
||||
attribute_name = order_expression.expr.name
|
||||
|
||||
column_nullable = model_class.columns.find { |column| column.name == attribute_name }.null
|
||||
|
||||
nullable = if column_nullable && order_expression.is_a?(Arel::Nodes::Ascending)
|
||||
:nulls_last
|
||||
elsif column_nullable && order_expression.is_a?(Arel::Nodes::Descending)
|
||||
:nulls_first
|
||||
else
|
||||
:not_nullable
|
||||
end
|
||||
|
||||
Gitlab::Pagination::Keyset::Order.build([
|
||||
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
|
||||
attribute_name: attribute_name,
|
||||
order_expression: order_expression,
|
||||
nullable: nullable,
|
||||
distinct: false
|
||||
),
|
||||
tie_breaker_column_order
|
||||
])
|
||||
end
|
||||
|
||||
def ordered_by_primary_key?
|
||||
return unless order_values.one?
|
||||
|
||||
attribute = order_values.first.try(:expr)
|
||||
|
||||
return unless attribute
|
||||
|
||||
arel_table[primary_key].to_s == attribute.to_s
|
||||
end
|
||||
|
||||
def ordered_by_other_column?
|
||||
return unless order_values.one?
|
||||
|
||||
attribute = order_values.first.try(:expr)
|
||||
|
||||
return unless attribute
|
||||
return unless attribute.try(:name)
|
||||
|
||||
model_class.column_names.include?(attribute.name.to_s)
|
||||
end
|
||||
|
||||
def ordered_by_other_column_with_tie_breaker?
|
||||
return unless order_values.size == 2
|
||||
|
||||
attribute = order_values.first.try(:expr)
|
||||
tie_breaker_attribute = order_values.second.try(:expr)
|
||||
|
||||
return unless attribute
|
||||
return unless tie_breaker_attribute
|
||||
|
||||
model_class.column_names.include?(attribute.name.to_s) &&
|
||||
arel_table[primary_key].to_s == tie_breaker_attribute.to_s
|
||||
end
|
||||
|
||||
def default_tie_breaker_column_order
|
||||
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
|
||||
attribute_name: model_class.primary_key,
|
||||
order_expression: arel_table[primary_key].desc
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -164,7 +164,7 @@ module Gitlab
|
|||
projects_with_repositories_enabled: count(ProjectFeature.where('repository_access_level > ?', ProjectFeature::DISABLED)),
|
||||
projects_with_tracing_enabled: count(ProjectTracingSetting),
|
||||
projects_with_error_tracking_enabled: count(::ErrorTracking::ProjectErrorTrackingSetting.where(enabled: true)),
|
||||
projects_with_alerts_service_enabled: count(Service.active.where(type: 'AlertsService')),
|
||||
projects_with_alerts_service_enabled: DEPRECATED_VALUE,
|
||||
projects_with_alerts_created: distinct_count(::AlertManagement::Alert, :project_id),
|
||||
projects_with_enabled_alert_integrations: distinct_count(::AlertManagement::HttpIntegration.active, :project_id),
|
||||
projects_with_prometheus_alerts: distinct_count(PrometheusAlert, :project_id),
|
||||
|
|
|
@ -691,6 +691,9 @@ msgstr ""
|
|||
msgid "%{milliseconds}ms"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{model_name} not found"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{mrText}, this issue will be closed automatically."
|
||||
msgstr ""
|
||||
|
||||
|
@ -5786,6 +5789,9 @@ msgstr ""
|
|||
msgid "Cannot create the abuse report. This user has been blocked."
|
||||
msgstr ""
|
||||
|
||||
msgid "Cannot delete %{profile_name} referenced in security policy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cannot enable shared runners because parent group does not allow it"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5807,6 +5813,9 @@ msgstr ""
|
|||
msgid "Cannot merge"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cannot modify %{profile_name} referenced in security policy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cannot modify managed Kubernetes cluster"
|
||||
msgstr ""
|
||||
|
||||
|
@ -10115,6 +10124,9 @@ msgstr ""
|
|||
msgid "DastProfiles|Request headers"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Rest API"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Run scan"
|
||||
msgstr ""
|
||||
|
||||
|
@ -10160,6 +10172,9 @@ msgstr ""
|
|||
msgid "DastProfiles|Site name"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Site type"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Spider timeout"
|
||||
msgstr ""
|
||||
|
||||
|
@ -10208,6 +10223,9 @@ msgstr ""
|
|||
msgid "DastProfiles|Validation status"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Website"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Copy HTTP header to clipboard"
|
||||
msgstr ""
|
||||
|
||||
|
@ -17185,6 +17203,9 @@ msgstr ""
|
|||
msgid "Instance overview"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insufficient permissions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integration"
|
||||
msgstr ""
|
||||
|
||||
|
@ -27661,6 +27682,12 @@ msgstr ""
|
|||
msgid "Scanner"
|
||||
msgstr ""
|
||||
|
||||
msgid "Scanner profile failed to delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Scanner profile not found for given parameters"
|
||||
msgstr ""
|
||||
|
||||
msgid "Schedule a new pipeline"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29310,6 +29337,12 @@ msgstr ""
|
|||
msgid "Single or combined queries"
|
||||
msgstr ""
|
||||
|
||||
msgid "Site profile failed to delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Site profile not found for given parameters"
|
||||
msgstr ""
|
||||
|
||||
msgid "Size"
|
||||
msgstr ""
|
||||
|
||||
|
@ -36162,7 +36195,10 @@ msgstr ""
|
|||
msgid "You have been granted %{access_level} access to the %{source_name} %{source_type}."
|
||||
msgstr ""
|
||||
|
||||
msgid "You have been granted %{member_human_access} access to %{title} %{name}."
|
||||
msgid "You have been granted %{member_human_access} access to group %{name}."
|
||||
msgstr ""
|
||||
|
||||
msgid "You have been granted %{member_human_access} access to project %{name}."
|
||||
msgstr ""
|
||||
|
||||
msgid "You have been invited"
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
function retrieve_tests_metadata() {
|
||||
mkdir -p knapsack/ rspec_flaky/ rspec_profiling/
|
||||
|
||||
# ${CI_DEFAULT_BRANCH} might not be master in other forks but we want to
|
||||
# always target the canonical project here, so the branch must be hardcoded
|
||||
local project_path="gitlab-org/gitlab"
|
||||
local artifact_branch="master"
|
||||
local test_metadata_job_id
|
||||
|
||||
# Ruby
|
||||
test_metadata_job_id=$(scripts/api/get_job_id.rb --project "${project_path}" -q "status=success" -q "ref=${CI_DEFAULT_BRANCH}" -q "username=gitlab-bot" -Q "scope=success" --job-name "update-tests-metadata")
|
||||
test_metadata_job_id=$(scripts/api/get_job_id.rb --project "${project_path}" -q "status=success" -q "ref=${artifact_branch}" -q "username=gitlab-bot" -Q "scope=success" --job-name "update-tests-metadata")
|
||||
|
||||
if [[ ! -f "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" ]]; then
|
||||
scripts/api/download_job_artifact.rb --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
|
||||
|
|
|
@ -32,8 +32,6 @@ FactoryBot.define do
|
|||
create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true)
|
||||
create(:project_error_tracking_setting, project: projects[0])
|
||||
create(:project_error_tracking_setting, project: projects[1], enabled: false)
|
||||
create(:service, project: projects[0], type: 'AlertsService', active: true)
|
||||
create(:service, project: projects[1], type: 'AlertsService', active: false)
|
||||
alert_bot_issues = create_list(:incident, 2, project: projects[0], author: User.alert_bot)
|
||||
create_list(:incident, 2, project: projects[1], author: User.alert_bot)
|
||||
issues = create_list(:issue, 4, project: projects[0])
|
||||
|
|
|
@ -64,13 +64,12 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
|
|||
expect(find_field('Email').value).to eq(group_invite.invite_email)
|
||||
end
|
||||
|
||||
it 'sign in, grants access and redirects to group page' do
|
||||
it 'sign in, grants access and redirects to group activity page' do
|
||||
click_link 'Sign in'
|
||||
|
||||
fill_in_sign_in_form(user)
|
||||
|
||||
expect(current_path).to eq(group_path(group))
|
||||
expect(page).to have_content('You have been granted Developer access to group Owned.')
|
||||
expect(current_path).to eq(activity_group_path(group))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -117,26 +116,28 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
|
|||
context 'email confirmation disabled' do
|
||||
let(:send_email_confirmation) { false }
|
||||
|
||||
it 'signs up and redirects to the dashboard page with all the projects/groups invitations automatically accepted' do
|
||||
it 'signs up and redirects to the most recent membership activity page with all the projects/groups invitations automatically accepted' do
|
||||
fill_in_sign_up_form(new_user)
|
||||
fill_in_welcome_form
|
||||
|
||||
expect(current_path).to eq(dashboard_projects_path)
|
||||
expect(page).to have_content(project.full_name)
|
||||
expect(current_path).to eq(activity_group_path(group))
|
||||
expect(page).to have_content('You have been granted Owner access to group Owned.')
|
||||
|
||||
visit group_path(group)
|
||||
|
||||
expect(page).to have_content(group.full_name)
|
||||
|
||||
visit project_path(project)
|
||||
expect(page).to have_content(project.name)
|
||||
end
|
||||
|
||||
context 'the user sign-up using a different email address' do
|
||||
let(:invite_email) { build_stubbed(:user).email }
|
||||
|
||||
it 'signs up and redirects to the invitation page' do
|
||||
it 'signs up and redirects to the activity page' do
|
||||
fill_in_sign_up_form(new_user)
|
||||
fill_in_welcome_form
|
||||
|
||||
expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
|
||||
expect(current_path).to eq(activity_group_path(group))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -207,7 +208,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
|
|||
fill_in_sign_in_form(new_user)
|
||||
fill_in_welcome_form
|
||||
|
||||
expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
|
||||
expect(current_path).to eq(activity_group_path(group))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -221,7 +222,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
|
|||
fill_in_sign_up_form(new_user)
|
||||
fill_in_welcome_form
|
||||
|
||||
expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
|
||||
expect(current_path).to eq(activity_group_path(group))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -273,7 +274,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when accepting the invitation' do
|
||||
context 'when accepting the invitation as an existing user' do
|
||||
let(:send_email_confirmation) { true }
|
||||
|
||||
before do
|
||||
|
@ -286,7 +287,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
|
|||
|
||||
page.click_link 'Accept invitation'
|
||||
|
||||
expect(current_path).to eq(group_path(group))
|
||||
expect(current_path).to eq(activity_group_path(group))
|
||||
expect(page).to have_content('You have been granted Owner access to group Owned.')
|
||||
expect(group.users.include?(user)).to be true
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe EnvironmentNamesFinder do
|
||||
RSpec.describe Environments::EnvironmentNamesFinder do
|
||||
describe '#execute' do
|
||||
let!(:group) { create(:group) }
|
||||
let!(:public_project) { create(:project, :public, namespace: group) }
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe EnvironmentsByDeploymentsFinder do
|
||||
RSpec.describe Environments::EnvironmentsByDeploymentsFinder do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { project.creator }
|
||||
let(:environment) { create(:environment, :available, project: project) }
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe EnvironmentsFinder do
|
||||
RSpec.describe Environments::EnvironmentsFinder do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { project.creator }
|
||||
let(:environment) { create(:environment, :available, project: project) }
|
|
@ -357,9 +357,10 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
|
|||
|
||||
it 'is added to end' do
|
||||
sliced = subject.sliced_nodes
|
||||
last_order_name = sliced.order_values.last.expr.name
|
||||
|
||||
expect(last_order_name).to eq sliced.primary_key
|
||||
order_sql = sliced.order_values.last.to_sql
|
||||
|
||||
expect(order_sql).to end_with(Project.arel_table[:id].desc.to_sql)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Pagination::Keyset::SimpleOrderBuilder do
|
||||
let(:ordered_scope) { described_class.build(scope).first }
|
||||
let(:order_object) { Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(ordered_scope) }
|
||||
|
||||
subject(:sql_with_order) { ordered_scope.to_sql }
|
||||
|
||||
context 'when no order present' do
|
||||
let(:scope) { Project.where(id: [1, 2, 3]) }
|
||||
|
||||
it 'orders by primary key' do
|
||||
expect(sql_with_order).to end_with('ORDER BY "projects"."id" DESC')
|
||||
end
|
||||
|
||||
it 'sets the column definition distinct and not nullable' do
|
||||
column_definition = order_object.column_definitions.first
|
||||
|
||||
expect(column_definition).to be_not_nullable
|
||||
expect(column_definition).to be_distinct
|
||||
end
|
||||
end
|
||||
|
||||
context 'when primary key order present' do
|
||||
let(:scope) { Project.where(id: [1, 2, 3]).order(id: :asc) }
|
||||
|
||||
it 'orders by primary key without altering the direction' do
|
||||
expect(sql_with_order).to end_with('ORDER BY "projects"."id" ASC')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ordered by other column' do
|
||||
let(:scope) { Project.where(id: [1, 2, 3]).order(created_at: :asc) }
|
||||
|
||||
it 'adds extra primary key order as tie-breaker' do
|
||||
expect(sql_with_order).to end_with('ORDER BY "projects"."created_at" ASC, "projects"."id" DESC')
|
||||
end
|
||||
|
||||
it 'sets the column definition for created_at non-distinct and nullable' do
|
||||
column_definition = order_object.column_definitions.first
|
||||
|
||||
expect(column_definition.attribute_name).to eq('created_at')
|
||||
expect(column_definition.nullable?).to eq(true) # be_nullable calls non_null? method for some reason
|
||||
expect(column_definition).not_to be_distinct
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ordered by two columns where the last one is the tie breaker' do
|
||||
let(:scope) { Project.where(id: [1, 2, 3]).order(created_at: :asc, id: :asc) }
|
||||
|
||||
it 'preserves the order' do
|
||||
expect(sql_with_order).to end_with('ORDER BY "projects"."created_at" ASC, "projects"."id" ASC')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when non-nullable column is given' do
|
||||
let(:scope) { Project.where(id: [1, 2, 3]).order(namespace_id: :asc, id: :asc) }
|
||||
|
||||
it 'sets the column definition for namespace_id non-distinct and non-nullable' do
|
||||
column_definition = order_object.column_definitions.first
|
||||
|
||||
expect(column_definition.attribute_name).to eq('namespace_id')
|
||||
expect(column_definition).to be_not_nullable
|
||||
expect(column_definition).not_to be_distinct
|
||||
end
|
||||
end
|
||||
|
||||
context 'return :unable_to_order symbol when order cannot be built' do
|
||||
subject(:success) { described_class.build(scope).last }
|
||||
|
||||
context 'when raw SQL order is given' do
|
||||
let(:scope) { Project.order('id DESC') }
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
|
||||
context 'when NULLS LAST order is given' do
|
||||
let(:scope) { Project.order(::Gitlab::Database.nulls_last_order('created_at', 'ASC')) }
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
|
||||
context 'when more than 2 columns are given for the order' do
|
||||
let(:scope) { Project.order(created_at: :asc, updated_at: :desc, id: :asc) }
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -571,7 +571,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
|
|||
expect(count_data[:projects_with_repositories_enabled]).to eq(3)
|
||||
expect(count_data[:projects_with_error_tracking_enabled]).to eq(1)
|
||||
expect(count_data[:projects_with_tracing_enabled]).to eq(1)
|
||||
expect(count_data[:projects_with_alerts_service_enabled]).to eq(1)
|
||||
expect(count_data[:projects_with_alerts_service_enabled]).to eq(Gitlab::UsageData::DEPRECATED_VALUE)
|
||||
expect(count_data[:projects_with_enabled_alert_integrations]).to eq(1)
|
||||
expect(count_data[:projects_with_prometheus_alerts]).to eq(2)
|
||||
expect(count_data[:projects_with_terraform_reports]).to eq(2)
|
||||
|
|
|
@ -120,6 +120,12 @@ RSpec.describe BroadcastMessage do
|
|||
expect(subject.call('/users/name/issues').length).to eq(1)
|
||||
end
|
||||
|
||||
it 'returns message if provided a path without a preceding slash' do
|
||||
create(:broadcast_message, target_path: "/users/*/issues", broadcast_type: broadcast_type)
|
||||
|
||||
expect(subject.call('users/name/issues').length).to eq(1)
|
||||
end
|
||||
|
||||
it 'returns the message for empty target path' do
|
||||
create(:broadcast_message, target_path: "", broadcast_type: broadcast_type)
|
||||
|
||||
|
|
|
@ -283,25 +283,50 @@ RSpec.describe 'GraphQL' do
|
|||
)
|
||||
end
|
||||
|
||||
it 'paginates datetimes correctly when they have millisecond data' do
|
||||
# let's make sure we're actually querying a timestamp, just in case
|
||||
expect(Gitlab::Graphql::Pagination::Keyset::QueryBuilder)
|
||||
.to receive(:new).with(anything, anything, hash_including('created_at'), anything).and_call_original
|
||||
context 'when new_graphql_keyset_pagination feature flag is off' do
|
||||
before do
|
||||
stub_feature_flags(new_graphql_keyset_pagination: false)
|
||||
end
|
||||
|
||||
execute_query
|
||||
first_page = graphql_data
|
||||
edges = first_page.dig(*issues_edges)
|
||||
cursor = first_page.dig(*end_cursor)
|
||||
it 'paginates datetimes correctly when they have millisecond data' do
|
||||
# let's make sure we're actually querying a timestamp, just in case
|
||||
expect(Gitlab::Graphql::Pagination::Keyset::QueryBuilder)
|
||||
.to receive(:new).with(anything, anything, hash_including('created_at'), anything).and_call_original
|
||||
|
||||
expect(edges.count).to eq(6)
|
||||
expect(edges.last['node']['iid']).to eq(issues[4].iid.to_s)
|
||||
execute_query
|
||||
first_page = graphql_data
|
||||
edges = first_page.dig(*issues_edges)
|
||||
cursor = first_page.dig(*end_cursor)
|
||||
|
||||
execute_query(after: cursor)
|
||||
second_page = graphql_data
|
||||
edges = second_page.dig(*issues_edges)
|
||||
expect(edges.count).to eq(6)
|
||||
expect(edges.last['node']['iid']).to eq(issues[4].iid.to_s)
|
||||
|
||||
expect(edges.count).to eq(4)
|
||||
expect(edges.last['node']['iid']).to eq(issues[0].iid.to_s)
|
||||
execute_query(after: cursor)
|
||||
second_page = graphql_data
|
||||
edges = second_page.dig(*issues_edges)
|
||||
|
||||
expect(edges.count).to eq(4)
|
||||
expect(edges.last['node']['iid']).to eq(issues[0].iid.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when new_graphql_keyset_pagination feature flag is on' do
|
||||
it 'paginates datetimes correctly when they have millisecond data' do
|
||||
execute_query
|
||||
first_page = graphql_data
|
||||
edges = first_page.dig(*issues_edges)
|
||||
cursor = first_page.dig(*end_cursor)
|
||||
|
||||
expect(edges.count).to eq(6)
|
||||
expect(edges.last['node']['iid']).to eq(issues[4].iid.to_s)
|
||||
|
||||
execute_query(after: cursor)
|
||||
second_page = graphql_data
|
||||
edges = second_page.dig(*issues_edges)
|
||||
|
||||
expect(edges.count).to eq(4)
|
||||
expect(edges.last['node']['iid']).to eq(issues[0].iid.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,6 +63,7 @@ RSpec.describe Lfs::PushService do
|
|||
it 'returns a failure when submitting a batch fails' do
|
||||
expect(lfs_client).to receive(:batch!) { raise 'failed' }
|
||||
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original
|
||||
expect(service.execute).to eq(status: :error, message: 'failed')
|
||||
end
|
||||
|
||||
|
@ -70,6 +71,7 @@ RSpec.describe Lfs::PushService do
|
|||
stub_lfs_batch(lfs_object)
|
||||
expect(lfs_client).to receive(:upload!) { raise 'failed' }
|
||||
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original
|
||||
expect(service.execute).to eq(status: :error, message: 'failed')
|
||||
end
|
||||
|
||||
|
|
|
@ -264,7 +264,7 @@ RSpec.describe PostReceiveService do
|
|||
|
||||
context "project path matches" do
|
||||
before do
|
||||
allow(project).to receive(:full_path).and_return("/company/sekrit-project")
|
||||
allow(project).to receive(:full_path).and_return("company/sekrit-project")
|
||||
end
|
||||
|
||||
it "does output the latest scoped broadcast message" do
|
||||
|
|
|
@ -273,16 +273,6 @@ RSpec.describe Projects::CreateService, '#execute' do
|
|||
opts[:default_branch] = 'master'
|
||||
expect(create_project(user, opts)).to eq(nil)
|
||||
end
|
||||
|
||||
it 'sets invalid service as inactive' do
|
||||
create(:service, type: 'JiraService', project: nil, template: true, active: true)
|
||||
|
||||
project = create_project(user, opts)
|
||||
service = project.services.first
|
||||
|
||||
expect(project).to be_persisted
|
||||
expect(service.active).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'wiki_enabled creates repository directory' do
|
||||
|
@ -633,17 +623,6 @@ RSpec.describe Projects::CreateService, '#execute' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is an invalid integration' do
|
||||
before do
|
||||
create(:service, :template, type: 'DroneCiService', active: true)
|
||||
end
|
||||
|
||||
it 'creates an inactive service' do
|
||||
expect(project).to be_persisted
|
||||
expect(project.services.first.active).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when skip_disk_validation is used' do
|
||||
|
|
|
@ -22,7 +22,6 @@ RSpec.describe BuildFinishedWorker do
|
|||
end
|
||||
|
||||
expect(BuildHooksWorker).to receive(:perform_async)
|
||||
expect(ExpirePipelineCacheWorker).to receive(:perform_async)
|
||||
expect(ChatNotificationWorker).not_to receive(:perform_async)
|
||||
expect(ArchiveTraceWorker).to receive(:perform_in)
|
||||
|
||||
|
|
Loading…
Reference in New Issue