Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7172fb1031
commit
7d4b2ed7bf
|
|
@ -53,6 +53,8 @@ workflow:
|
|||
variables:
|
||||
RAILS_ENV: "test"
|
||||
NODE_ENV: "test"
|
||||
BUNDLE_WITHOUT: "production:development"
|
||||
BUNDLE_INSTALL_FLAGS: "--jobs=$(nproc) --retry=3 --quiet"
|
||||
# we override the max_old_space_size to prevent OOM errors
|
||||
NODE_OPTIONS: --max_old_space_size=3584
|
||||
SIMPLECOV: "true"
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@
|
|||
- .qa-cache
|
||||
stage: test
|
||||
needs: []
|
||||
variables:
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
SETUP_DB: "false"
|
||||
before_script:
|
||||
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb'
|
||||
- !reference [.default-before_script, before_script]
|
||||
- cd qa/
|
||||
- bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --without=development --quiet
|
||||
- bundle check
|
||||
- bundle_install_script
|
||||
|
||||
qa:internal:
|
||||
extends:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
|
||||
.minimal-bundle-install:
|
||||
script:
|
||||
- run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --without default development test production puma unicorn kerberos metrics omnibus ed25519"
|
||||
- export BUNDLE_WITHOUT="${BUNDLE_WITHOUT}:default:test:puma:unicorn:kerberos:metrics:omnibus:ed25519"
|
||||
- bundle_install_script
|
||||
|
||||
.base-script:
|
||||
script:
|
||||
|
|
|
|||
|
|
@ -218,8 +218,8 @@ danger-review:
|
|||
stage: test
|
||||
needs: []
|
||||
before_script:
|
||||
- source ./scripts/utils.sh
|
||||
- run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --with danger"
|
||||
- source scripts/utils.sh
|
||||
- bundle_install_script "--with danger"
|
||||
- run_timed_command "retry yarn install --frozen-lockfile"
|
||||
script:
|
||||
- >
|
||||
|
|
|
|||
|
|
@ -35,8 +35,9 @@ export default {
|
|||
inject: {
|
||||
newUserListPath: { default: '' },
|
||||
newFeatureFlagPath: { default: '' },
|
||||
canUserConfigure: { required: true },
|
||||
featureFlagsLimitExceeded: { required: true },
|
||||
canUserConfigure: {},
|
||||
featureFlagsLimitExceeded: {},
|
||||
featureFlagsLimit: {},
|
||||
},
|
||||
data() {
|
||||
const scope = getParameterByName('scope') || SCOPES.FEATURE_FLAG_SCOPE;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export default () => {
|
|||
newFeatureFlagPath,
|
||||
newUserListPath,
|
||||
featureFlagsLimitExceeded,
|
||||
featureFlagsLimit,
|
||||
} = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
|
|
@ -40,7 +41,8 @@ export default () => {
|
|||
canUserConfigure: canUserAdminFeatureFlag !== undefined,
|
||||
newFeatureFlagPath,
|
||||
newUserListPath,
|
||||
featureFlagsLimitExceeded,
|
||||
featureFlagsLimitExceeded: featureFlagsLimitExceeded !== undefined,
|
||||
featureFlagsLimit,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(FeatureFlagsComponent);
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ class Admin::RunnersController < Admin::ApplicationController
|
|||
|
||||
feature_category :continuous_integration
|
||||
|
||||
NUMBER_OF_RUNNERS_PER_PAGE = 30
|
||||
|
||||
def index
|
||||
finder = Ci::RunnersFinder.new(current_user: current_user, params: params)
|
||||
@runners = finder.execute
|
||||
@runners = finder.execute.page(params[:page]).per(NUMBER_OF_RUNNERS_PER_PAGE)
|
||||
@active_runners_count = Ci::Runner.online.count
|
||||
@sort = finder.sort_key
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ class Admin::UsersController < Admin::ApplicationController
|
|||
before_action :user, except: [:index, :cohorts, :new, :create]
|
||||
before_action :check_impersonation_availability, only: :impersonate
|
||||
before_action :ensure_destroy_prerequisites_met, only: [:destroy]
|
||||
before_action :check_ban_user_feature_flag, only: [:ban]
|
||||
|
||||
feature_category :users
|
||||
|
||||
|
|
@ -130,6 +131,24 @@ class Admin::UsersController < Admin::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def ban
|
||||
result = Users::BanService.new(current_user).execute(user)
|
||||
|
||||
if result[:status] == :success
|
||||
redirect_back_or_admin_user(notice: _("Successfully banned"))
|
||||
else
|
||||
redirect_back_or_admin_user(alert: _("Error occurred. User was not banned"))
|
||||
end
|
||||
end
|
||||
|
||||
def unban
|
||||
if update_user { |user| user.activate }
|
||||
redirect_back_or_admin_user(notice: _("Successfully unbanned"))
|
||||
else
|
||||
redirect_back_or_admin_user(alert: _("Error occurred. User was not unbanned"))
|
||||
end
|
||||
end
|
||||
|
||||
def unlock
|
||||
if update_user { |user| user.unlock_access! }
|
||||
redirect_back_or_admin_user(alert: _("Successfully unlocked"))
|
||||
|
|
@ -325,6 +344,10 @@ class Admin::UsersController < Admin::ApplicationController
|
|||
access_denied! unless Gitlab.config.gitlab.impersonation_enabled
|
||||
end
|
||||
|
||||
def check_ban_user_feature_flag
|
||||
access_denied! unless Feature.enabled?(:ban_user_feature_flag)
|
||||
end
|
||||
|
||||
def log_impersonation_event
|
||||
Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username })
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Projects::Analytics::CycleAnalytics::StagesController < Projects::ApplicationController
|
||||
respond_to :json
|
||||
|
||||
feature_category :planning_analytics
|
||||
|
||||
before_action :authorize_read_cycle_analytics!
|
||||
before_action :only_default_value_stream_is_allowed!
|
||||
|
||||
def index
|
||||
result = list_service.execute
|
||||
|
||||
if result.success?
|
||||
render json: cycle_analytics_configuration(result.payload[:stages])
|
||||
else
|
||||
render json: { message: result.message }, status: result.http_status
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def only_default_value_stream_is_allowed!
|
||||
render_404 if params[:value_stream_id] != Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME
|
||||
end
|
||||
|
||||
def value_stream
|
||||
Analytics::CycleAnalytics::ProjectValueStream.build_default_value_stream(@project)
|
||||
end
|
||||
|
||||
def list_params
|
||||
{ value_stream: value_stream }
|
||||
end
|
||||
|
||||
def list_service
|
||||
Analytics::CycleAnalytics::Stages::ListService.new(parent: @project, current_user: current_user, params: list_params)
|
||||
end
|
||||
|
||||
def cycle_analytics_configuration(stages)
|
||||
stage_presenters = stages.map { |s| ::Analytics::CycleAnalytics::StagePresenter.new(s) }
|
||||
|
||||
Analytics::CycleAnalytics::ConfigurationEntity.new(stages: stage_presenters)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Projects::Analytics::CycleAnalytics::ValueStreamsController < Projects::ApplicationController
|
||||
respond_to :json
|
||||
|
||||
feature_category :planning_analytics
|
||||
|
||||
before_action :authorize_read_cycle_analytics!
|
||||
|
||||
def index
|
||||
# FOSS users can only see the default value stream
|
||||
value_streams = [Analytics::CycleAnalytics::ProjectValueStream.build_default_value_stream(@project)]
|
||||
|
||||
render json: Analytics::CycleAnalytics::ValueStreamSerializer.new.represent(value_streams)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
module CycleAnalytics
|
||||
class StageFinder
|
||||
def initialize(parent:, stage_id:)
|
||||
@parent = parent
|
||||
@stage_id = stage_id
|
||||
end
|
||||
|
||||
def execute
|
||||
build_in_memory_stage_by_name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :parent, :stage_id
|
||||
|
||||
def build_in_memory_stage_by_name
|
||||
parent.cycle_analytics_stages.build(find_in_memory_stage)
|
||||
end
|
||||
|
||||
def find_in_memory_stage
|
||||
# raise ActiveRecord::RecordNotFound, so it will behave similarly to AR models and produce 404 response in the controller
|
||||
raw_stage = Gitlab::Analytics::CycleAnalytics::DefaultStages.all.find do |hash|
|
||||
hash[:name].eql?(stage_id)
|
||||
end
|
||||
|
||||
raise(ActiveRecord::RecordNotFound, "Stage with id '#{stage_id}' could not be found") unless raw_stage
|
||||
|
||||
raw_stage
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Analytics::CycleAnalytics::StageFinder.prepend_mod_with('Analytics::CycleAnalytics::StageFinder')
|
||||
|
|
@ -4,8 +4,6 @@ module Ci
|
|||
class RunnersFinder < UnionFinder
|
||||
include Gitlab::Allowable
|
||||
|
||||
NUMBER_OF_RUNNERS_PER_PAGE = 30
|
||||
|
||||
def initialize(current_user:, group: nil, params:)
|
||||
@params = params
|
||||
@group = group
|
||||
|
|
@ -18,7 +16,6 @@ module Ci
|
|||
filter_by_runner_type!
|
||||
filter_by_tag_list!
|
||||
sort!
|
||||
paginate!
|
||||
|
||||
@runners.with_tags
|
||||
|
||||
|
|
@ -77,10 +74,6 @@ module Ci
|
|||
@runners = @runners.order_by(sort_key)
|
||||
end
|
||||
|
||||
def paginate!
|
||||
@runners = @runners.page(@params[:page]).per(NUMBER_OF_RUNNERS_PER_PAGE)
|
||||
end
|
||||
|
||||
def filter_by!(scope_name, available_scopes)
|
||||
scope = @params[scope_name]
|
||||
|
||||
|
|
|
|||
|
|
@ -162,6 +162,49 @@ module UsersHelper
|
|||
header + list
|
||||
end
|
||||
|
||||
def user_ban_data(user)
|
||||
{
|
||||
path: ban_admin_user_path(user),
|
||||
method: 'put',
|
||||
modal_attributes: {
|
||||
title: s_('AdminUsers|Ban user %{username}?') % { username: sanitize_name(user.name) },
|
||||
message: s_('AdminUsers|You can unban their account in the future. Their data remains intact.'),
|
||||
okVariant: 'warning',
|
||||
okTitle: s_('AdminUsers|Ban')
|
||||
}.to_json
|
||||
}
|
||||
end
|
||||
|
||||
def user_unban_data(user)
|
||||
{
|
||||
path: unban_admin_user_path(user),
|
||||
method: 'put',
|
||||
modal_attributes: {
|
||||
title: s_('AdminUsers|Unban %{username}?') % { username: sanitize_name(user.name) },
|
||||
message: s_('AdminUsers|You ban their account in the future if necessary.'),
|
||||
okVariant: 'info',
|
||||
okTitle: s_('AdminUsers|Unban')
|
||||
}.to_json
|
||||
}
|
||||
end
|
||||
|
||||
def user_ban_effects
|
||||
header = tag.p s_('AdminUsers|Banning the user has the following effects:')
|
||||
|
||||
list = tag.ul do
|
||||
concat tag.li s_('AdminUsers|User will be blocked')
|
||||
end
|
||||
|
||||
link_start = '<a href="%{url}" target="_blank">'.html_safe % { url: help_page_path("user/admin_area/moderate_users", anchor: "ban-a-user") }
|
||||
info = tag.p s_('AdminUsers|Learn more about %{link_start}banned users.%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
||||
|
||||
header + list + info
|
||||
end
|
||||
|
||||
def ban_feature_available?
|
||||
Feature.enabled?(:ban_user_feature_flag)
|
||||
end
|
||||
|
||||
def user_deactivation_data(user, message)
|
||||
{
|
||||
path: deactivate_admin_user_path(user),
|
||||
|
|
@ -235,6 +278,9 @@ module UsersHelper
|
|||
pending_approval_badge = { text: s_('AdminUsers|Pending approval'), variant: 'info' }
|
||||
return pending_approval_badge if user.blocked_pending_approval?
|
||||
|
||||
banned_badge = { text: s_('AdminUsers|Banned'), variant: 'danger' }
|
||||
return banned_badge if user.banned?
|
||||
|
||||
{ text: s_('AdminUsers|Blocked'), variant: 'danger' }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,13 @@ module Analytics
|
|||
|
||||
validates :project, presence: true
|
||||
belongs_to :project
|
||||
belongs_to :value_stream, class_name: 'Analytics::CycleAnalytics::ProjectValueStream', foreign_key: :project_value_stream_id
|
||||
|
||||
alias_attribute :parent, :project
|
||||
alias_attribute :parent_id, :project_id
|
||||
|
||||
alias_attribute :value_stream_id, :project_value_stream_id
|
||||
|
||||
delegate :group, to: :project
|
||||
|
||||
validate :validate_project_group_for_label_events, if: -> { start_event_label_based? || end_event_label_based? }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Analytics::CycleAnalytics::ProjectValueStream < ApplicationRecord
|
||||
belongs_to :project
|
||||
|
||||
has_many :stages, class_name: 'Analytics::CycleAnalytics::ProjectStage'
|
||||
|
||||
validates :project, :name, presence: true
|
||||
validates :name, length: { minimum: 3, maximum: 100, allow_nil: false }, uniqueness: { scope: :project_id }
|
||||
|
||||
def custom?
|
||||
false
|
||||
end
|
||||
|
||||
def stages
|
||||
[]
|
||||
end
|
||||
|
||||
def self.build_default_value_stream(project)
|
||||
new(name: Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME, project: project)
|
||||
end
|
||||
end
|
||||
|
|
@ -27,6 +27,7 @@ module Analytics
|
|||
scope :default_stages, -> { where(custom: false) }
|
||||
scope :ordered, -> { order(:relative_position, :id) }
|
||||
scope :for_list, -> { includes(:start_event_label, :end_event_label).ordered }
|
||||
scope :by_value_stream, -> (value_stream) { where(value_stream_id: value_stream.id) }
|
||||
end
|
||||
|
||||
def parent=(_)
|
||||
|
|
|
|||
|
|
@ -335,7 +335,8 @@ class Project < ApplicationRecord
|
|||
has_one :ci_cd_settings, class_name: 'ProjectCiCdSetting', inverse_of: :project, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
has_many :remote_mirrors, inverse_of: :project
|
||||
has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage'
|
||||
has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage', inverse_of: :project
|
||||
has_many :value_streams, class_name: 'Analytics::CycleAnalytics::ProjectValueStream', inverse_of: :project
|
||||
|
||||
has_many :external_pull_requests, inverse_of: :project
|
||||
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@ class User < ApplicationRecord
|
|||
transition deactivated: :blocked
|
||||
transition ldap_blocked: :blocked
|
||||
transition blocked_pending_approval: :blocked
|
||||
transition banned: :blocked
|
||||
end
|
||||
|
||||
event :ldap_block do
|
||||
|
|
@ -338,19 +339,24 @@ class User < ApplicationRecord
|
|||
transition blocked: :active
|
||||
transition ldap_blocked: :active
|
||||
transition blocked_pending_approval: :active
|
||||
transition banned: :active
|
||||
end
|
||||
|
||||
event :block_pending_approval do
|
||||
transition active: :blocked_pending_approval
|
||||
end
|
||||
|
||||
event :ban do
|
||||
transition active: :banned
|
||||
end
|
||||
|
||||
event :deactivate do
|
||||
# Any additional changes to this event should be also
|
||||
# reflected in app/workers/users/deactivate_dormant_users_worker.rb
|
||||
transition active: :deactivated
|
||||
end
|
||||
|
||||
state :blocked, :ldap_blocked, :blocked_pending_approval do
|
||||
state :blocked, :ldap_blocked, :blocked_pending_approval, :banned do
|
||||
def blocked?
|
||||
true
|
||||
end
|
||||
|
|
@ -377,6 +383,7 @@ class User < ApplicationRecord
|
|||
scope :instance_access_request_approvers_to_be_notified, -> { admins.active.order_recent_sign_in.limit(INSTANCE_ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT) }
|
||||
scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
|
||||
scope :blocked_pending_approval, -> { with_states(:blocked_pending_approval) }
|
||||
scope :banned, -> { with_states(:banned) }
|
||||
scope :external, -> { where(external: true) }
|
||||
scope :non_external, -> { where(external: false) }
|
||||
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
||||
|
|
@ -598,6 +605,8 @@ class User < ApplicationRecord
|
|||
blocked
|
||||
when 'blocked_pending_approval'
|
||||
blocked_pending_approval
|
||||
when 'banned'
|
||||
banned
|
||||
when 'two_factor_disabled'
|
||||
without_two_factor
|
||||
when 'two_factor_enabled'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
module CycleAnalytics
|
||||
class ConfigurationEntity < Grape::Entity
|
||||
include RequestAwareEntity
|
||||
|
||||
expose :events, using: Analytics::CycleAnalytics::EventEntity
|
||||
expose :stages, using: Analytics::CycleAnalytics::StageEntity
|
||||
|
||||
private
|
||||
|
||||
def events
|
||||
(stage_events.events - stage_events.internal_events).sort_by(&:name)
|
||||
end
|
||||
|
||||
def stage_events
|
||||
Gitlab::Analytics::CycleAnalytics::StageEvents
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
module CycleAnalytics
|
||||
class EventEntity < Grape::Entity
|
||||
expose :name
|
||||
expose :identifier
|
||||
expose :type
|
||||
expose :can_be_start_event?, as: :can_be_start_event
|
||||
expose :allowed_end_events
|
||||
|
||||
private
|
||||
|
||||
def type
|
||||
object.label_based? ? 'label' : 'simple'
|
||||
end
|
||||
|
||||
def can_be_start_event?
|
||||
pairing_rules.has_key?(object)
|
||||
end
|
||||
|
||||
def allowed_end_events
|
||||
pairing_rules.fetch(object, []).map do |event|
|
||||
event.identifier unless stage_events.internal_events.include?(event)
|
||||
end.compact
|
||||
end
|
||||
|
||||
def pairing_rules
|
||||
stage_events.pairing_rules
|
||||
end
|
||||
|
||||
def stage_events
|
||||
Gitlab::Analytics::CycleAnalytics::StageEvents
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
module CycleAnalytics
|
||||
class StageEntity < Grape::Entity
|
||||
expose :title
|
||||
expose :hidden
|
||||
expose :legend
|
||||
expose :description
|
||||
expose :id
|
||||
expose :custom
|
||||
expose :start_event_identifier, if: -> (s) { s.custom? }
|
||||
expose :end_event_identifier, if: -> (s) { s.custom? }
|
||||
expose :start_event_label, using: LabelEntity, if: -> (s) { s.start_event_label_based? }
|
||||
expose :end_event_label, using: LabelEntity, if: -> (s) { s.end_event_label_based? }
|
||||
expose :start_event_html_description
|
||||
expose :end_event_html_description
|
||||
|
||||
def id
|
||||
object.id || object.name
|
||||
end
|
||||
|
||||
def start_event_html_description
|
||||
html_description(object.start_event)
|
||||
end
|
||||
|
||||
def end_event_html_description
|
||||
html_description(object.end_event)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def html_description(event)
|
||||
Banzai::Renderer.render(event.markdown_description, { group: object.group, project: nil })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
module CycleAnalytics
|
||||
class ValueStreamEntity < Grape::Entity
|
||||
expose :name
|
||||
expose :id
|
||||
expose :is_custom do |object|
|
||||
object.custom?
|
||||
end
|
||||
expose :stages, using: Analytics::CycleAnalytics::StageEntity
|
||||
|
||||
private
|
||||
|
||||
def id
|
||||
object.id || object.name # use the name `default` if the record is not persisted
|
||||
end
|
||||
|
||||
def stages
|
||||
object.stages.map { |s| ::Analytics::CycleAnalytics::StagePresenter.new(s) } # rubocop: disable CodeReuse/Presenter
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
module CycleAnalytics
|
||||
class ValueStreamSerializer < BaseSerializer
|
||||
entity ::Analytics::CycleAnalytics::ValueStreamEntity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
module CycleAnalytics
|
||||
module Stages
|
||||
class BaseService
|
||||
include Gitlab::Allowable
|
||||
|
||||
DEFAULT_VALUE_STREAM_NAME = 'default'
|
||||
|
||||
def initialize(parent:, current_user:, params: {})
|
||||
@parent = parent
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :parent, :current_user, :params
|
||||
|
||||
def success(stage, http_status = :created)
|
||||
ServiceResponse.success(payload: { stage: stage }, http_status: http_status)
|
||||
end
|
||||
|
||||
def forbidden
|
||||
ServiceResponse.error(message: 'Forbidden', payload: {}, http_status: :forbidden)
|
||||
end
|
||||
|
||||
def build_default_stages
|
||||
Gitlab::Analytics::CycleAnalytics::DefaultStages.all.map do |stage_params|
|
||||
parent.cycle_analytics_stages.build(stage_params.merge(value_stream: value_stream))
|
||||
end
|
||||
end
|
||||
|
||||
def value_stream
|
||||
@value_stream ||= params[:value_stream]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Analytics::CycleAnalytics::Stages::BaseService.prepend_mod_with('Analytics::CycleAnalytics::Stages::BaseService')
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
module CycleAnalytics
|
||||
module Stages
|
||||
class ListService < Analytics::CycleAnalytics::Stages::BaseService
|
||||
def execute
|
||||
return forbidden unless allowed?
|
||||
|
||||
success(build_default_stages)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allowed?
|
||||
can?(current_user, :read_cycle_analytics, parent)
|
||||
end
|
||||
|
||||
def success(stages)
|
||||
ServiceResponse.success(payload: { stages: stages })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Analytics::CycleAnalytics::Stages::ListService.prepend_mod_with('Analytics::CycleAnalytics::Stages::ListService')
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class BanService < BaseService
|
||||
def initialize(current_user)
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def execute(user)
|
||||
if user.ban
|
||||
log_event(user)
|
||||
success
|
||||
else
|
||||
messages = user.errors.full_messages
|
||||
error(messages.uniq.join('. '))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def log_event(user)
|
||||
Gitlab::AppLogger.info(message: "User banned", user: "#{user.username}", email: "#{user.email}", banned_by: "#{current_user.username}", ip_address: "#{current_user.current_sign_in_ip}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
- if ban_feature_available?
|
||||
.card.border-warning
|
||||
.card-header.bg-warning.gl-text-white
|
||||
= s_('AdminUsers|Ban user')
|
||||
.card-body
|
||||
= user_ban_effects
|
||||
%br
|
||||
%button.btn.gl-button.btn-warning.js-confirm-modal-button{ data: user_ban_data(user) }
|
||||
= s_('AdminUsers|Ban user')
|
||||
|
|
@ -3,6 +3,9 @@
|
|||
- if @user.blocked_pending_approval?
|
||||
%span.cred
|
||||
= s_('AdminUsers|(Pending approval)')
|
||||
- elsif @user.banned?
|
||||
%span.cred
|
||||
= s_('AdminUsers|(Banned)')
|
||||
- elsif @user.blocked?
|
||||
%span.cred
|
||||
= s_('AdminUsers|(Blocked)')
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@
|
|||
= link_to admin_users_path(filter: "blocked") do
|
||||
= s_('AdminUsers|Blocked')
|
||||
%small.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= limited_counter_with_delimiter(User.blocked)
|
||||
- if ban_feature_available?
|
||||
= nav_link(html_options: { class: active_when(params[:filter] == 'banned') }) do
|
||||
= link_to admin_users_path(filter: "banned") do
|
||||
= s_('AdminUsers|Banned')
|
||||
%small.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= limited_counter_with_delimiter(User.banned)
|
||||
= nav_link(html_options: { class: "#{active_when(params[:filter] == 'blocked_pending_approval')} filter-blocked-pending-approval" }) do
|
||||
= link_to admin_users_path(filter: "blocked_pending_approval"), data: { qa_selector: 'pending_approval_tab' } do
|
||||
= s_('AdminUsers|Pending approval')
|
||||
|
|
|
|||
|
|
@ -176,6 +176,20 @@
|
|||
- if @user.blocked_pending_approval?
|
||||
= render 'admin/users/approve_user', user: @user
|
||||
= render 'admin/users/reject_pending_user', user: @user
|
||||
- elsif @user.banned?
|
||||
.gl-card.border-info.gl-mb-5
|
||||
.gl-card-header.gl-bg-blue-500.gl-text-white
|
||||
= _('This user is banned')
|
||||
.gl-card-body
|
||||
%p= _('A banned user cannot:')
|
||||
%ul
|
||||
%li= _('Log in')
|
||||
%li= _('Access Git repositories')
|
||||
- link_start = '<a href="%{url}" target="_blank">'.html_safe % { url: help_page_path("user/admin_area/moderate_users", anchor: "ban-a-user") }
|
||||
= s_('AdminUsers|Learn more about %{link_start}banned users.%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
||||
%p
|
||||
%button.btn.gl-button.btn-info.js-confirm-modal-button{ data: user_unban_data(@user) }
|
||||
= s_('AdminUsers|Unban user')
|
||||
- else
|
||||
.gl-card.border-info.gl-mb-5
|
||||
.gl-card-header.gl-bg-blue-500.gl-text-white
|
||||
|
|
@ -190,6 +204,7 @@
|
|||
= s_('AdminUsers|Unblock user')
|
||||
- elsif !@user.internal?
|
||||
= render 'admin/users/block_user', user: @user
|
||||
= render 'admin/users/ban_user', user: @user
|
||||
|
||||
- if @user.access_locked?
|
||||
.card.border-info.gl-mb-5
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ module Packages
|
|||
|
||||
::Packages::Nuget::UpdatePackageFromMetadataService.new(package_file).execute
|
||||
|
||||
rescue ::Packages::Nuget::MetadataExtractionService::ExtractionError,
|
||||
::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError => e
|
||||
rescue StandardError => e
|
||||
Gitlab::ErrorTracking.log_exception(e, project_id: package_file.project_id)
|
||||
package_file.package.update_column(:status, :error)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ module Packages
|
|||
|
||||
::Packages::Rubygems::ProcessGemService.new(package_file).execute
|
||||
|
||||
rescue ::Packages::Rubygems::ProcessGemService::ExtractionError => e
|
||||
rescue StandardError => e
|
||||
Gitlab::ErrorTracking.log_exception(e, project_id: package_file.project_id)
|
||||
package_file.package.update_column(:status, :error)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Log additional package extraction errors
|
||||
merge_request: 61745
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Create database structure to support project value streams
|
||||
merge_request: 60925
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Inject Feature Flags Limit Value
|
||||
merge_request: 61621
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Ban user state and UI
|
||||
merge_request: 61292
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: ban_user_feature_flag
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61292
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330667
|
||||
milestone: '13.12'
|
||||
type: development
|
||||
group: group::access
|
||||
default_enabled: false
|
||||
|
|
@ -21,6 +21,8 @@ namespace :admin do
|
|||
get :keys
|
||||
put :block
|
||||
put :unblock
|
||||
put :ban
|
||||
put :unban
|
||||
put :deactivate
|
||||
put :activate
|
||||
put :unlock
|
||||
|
|
|
|||
|
|
@ -267,6 +267,15 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
end
|
||||
get '/cycle_analytics', to: redirect('%{namespace_id}/%{project_id}/-/value_stream_analytics')
|
||||
|
||||
namespace :analytics do
|
||||
resource :cycle_analytics, only: :show, path: 'value_stream_analytics'
|
||||
scope module: :cycle_analytics, as: 'cycle_analytics', path: 'value_stream_analytics' do
|
||||
resources :value_streams, only: [:index] do
|
||||
resources :stages, only: [:index]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
concerns :clusterable
|
||||
|
||||
namespace :serverless do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateProjectValueStreams < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
INDEX_NAME = 'index_analytics_ca_project_value_streams_on_project_id_and_name'
|
||||
|
||||
def up
|
||||
create_table_with_constraints :analytics_cycle_analytics_project_value_streams do |t|
|
||||
t.timestamps_with_timezone
|
||||
t.references(:project,
|
||||
null: false,
|
||||
index: false,
|
||||
foreign_key: { to_table: :projects, on_delete: :cascade }
|
||||
)
|
||||
t.text :name, null: false
|
||||
t.index [:project_id, :name], unique: true, name: INDEX_NAME
|
||||
t.text_limit :name, 100
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
drop_table :analytics_cycle_analytics_project_value_streams
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProjectValueStreamIdToProjectStages < ActiveRecord::Migration[6.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_analytics_ca_project_stages_on_value_stream_id'
|
||||
|
||||
class ProjectValueStream < ActiveRecord::Base
|
||||
self.table_name = 'analytics_cycle_analytics_project_stages'
|
||||
|
||||
include EachBatch
|
||||
end
|
||||
|
||||
def up
|
||||
ProjectValueStream.reset_column_information
|
||||
# The table was never used, there is no user-facing code that modifies the table, it should be empty.
|
||||
# Since there is no functionality present that depends on this data, it's safe to delete the rows.
|
||||
ProjectValueStream.each_batch(of: 100) do |relation|
|
||||
relation.delete_all
|
||||
end
|
||||
|
||||
transaction do
|
||||
add_reference :analytics_cycle_analytics_project_stages, :project_value_stream, null: false, index: { name: INDEX_NAME }, foreign_key: { on_delete: :cascade, to_table: :analytics_cycle_analytics_project_value_streams }, type: :bigint # rubocop: disable Migration/AddReference, Rails/NotNullColumn
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
remove_reference :analytics_cycle_analytics_project_stages, :project_value_stream
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
de8bf6c02589bf308914d43e5cd44dae91d3bbabcdaafcebdb96fba0a09b20bc
|
||||
|
|
@ -0,0 +1 @@
|
|||
2fdcb66e511d8322ea8fc4de66ecce859f8e91b2a9da22336281a1e784d9b4a5
|
||||
|
|
@ -9051,7 +9051,8 @@ CREATE TABLE analytics_cycle_analytics_project_stages (
|
|||
end_event_label_id bigint,
|
||||
hidden boolean DEFAULT false NOT NULL,
|
||||
custom boolean DEFAULT true NOT NULL,
|
||||
name character varying(255) NOT NULL
|
||||
name character varying(255) NOT NULL,
|
||||
project_value_stream_id bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE analytics_cycle_analytics_project_stages_id_seq
|
||||
|
|
@ -9063,6 +9064,24 @@ CREATE SEQUENCE analytics_cycle_analytics_project_stages_id_seq
|
|||
|
||||
ALTER SEQUENCE analytics_cycle_analytics_project_stages_id_seq OWNED BY analytics_cycle_analytics_project_stages.id;
|
||||
|
||||
CREATE TABLE analytics_cycle_analytics_project_value_streams (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
project_id bigint NOT NULL,
|
||||
name text NOT NULL,
|
||||
CONSTRAINT check_9b1970a898 CHECK ((char_length(name) <= 100))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE analytics_cycle_analytics_project_value_streams_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE analytics_cycle_analytics_project_value_streams_id_seq OWNED BY analytics_cycle_analytics_project_value_streams.id;
|
||||
|
||||
CREATE TABLE analytics_devops_adoption_segment_selections (
|
||||
id bigint NOT NULL,
|
||||
segment_id bigint NOT NULL,
|
||||
|
|
@ -19369,6 +19388,8 @@ ALTER TABLE ONLY analytics_cycle_analytics_group_value_streams ALTER COLUMN id S
|
|||
|
||||
ALTER TABLE ONLY analytics_cycle_analytics_project_stages ALTER COLUMN id SET DEFAULT nextval('analytics_cycle_analytics_project_stages_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY analytics_cycle_analytics_project_value_streams ALTER COLUMN id SET DEFAULT nextval('analytics_cycle_analytics_project_value_streams_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY analytics_devops_adoption_segment_selections ALTER COLUMN id SET DEFAULT nextval('analytics_devops_adoption_segment_selections_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY analytics_devops_adoption_segments ALTER COLUMN id SET DEFAULT nextval('analytics_devops_adoption_segments_id_seq'::regclass);
|
||||
|
|
@ -20449,6 +20470,9 @@ ALTER TABLE ONLY analytics_cycle_analytics_group_value_streams
|
|||
ALTER TABLE ONLY analytics_cycle_analytics_project_stages
|
||||
ADD CONSTRAINT analytics_cycle_analytics_project_stages_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY analytics_cycle_analytics_project_value_streams
|
||||
ADD CONSTRAINT analytics_cycle_analytics_project_value_streams_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY analytics_devops_adoption_segment_selections
|
||||
ADD CONSTRAINT analytics_devops_adoption_segment_selections_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -22262,6 +22286,10 @@ CREATE INDEX index_analytics_ca_project_stages_on_relative_position ON analytics
|
|||
|
||||
CREATE INDEX index_analytics_ca_project_stages_on_start_event_label_id ON analytics_cycle_analytics_project_stages USING btree (start_event_label_id);
|
||||
|
||||
CREATE INDEX index_analytics_ca_project_stages_on_value_stream_id ON analytics_cycle_analytics_project_stages USING btree (project_value_stream_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_analytics_ca_project_value_streams_on_project_id_and_name ON analytics_cycle_analytics_project_value_streams USING btree (project_id, name);
|
||||
|
||||
CREATE INDEX index_analytics_cycle_analytics_group_stages_custom_only ON analytics_cycle_analytics_group_stages USING btree (id) WHERE (custom = true);
|
||||
|
||||
CREATE UNIQUE INDEX index_analytics_devops_adoption_segments_on_namespace_id ON analytics_devops_adoption_segments USING btree (namespace_id);
|
||||
|
|
@ -26519,6 +26547,9 @@ ALTER TABLE ONLY namespace_admin_notes
|
|||
ALTER TABLE ONLY web_hook_logs_archived
|
||||
ADD CONSTRAINT fk_rails_666826e111 FOREIGN KEY (web_hook_id) REFERENCES web_hooks(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY analytics_cycle_analytics_project_value_streams
|
||||
ADD CONSTRAINT fk_rails_669f4ba293 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY jira_imports
|
||||
ADD CONSTRAINT fk_rails_675d38c03b FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE SET NULL;
|
||||
|
||||
|
|
@ -26621,6 +26652,9 @@ ALTER TABLE ONLY ci_subscriptions_projects
|
|||
ALTER TABLE ONLY terraform_states
|
||||
ADD CONSTRAINT fk_rails_78f54ca485 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY analytics_cycle_analytics_project_stages
|
||||
ADD CONSTRAINT fk_rails_796a7dbc9c FOREIGN KEY (project_value_stream_id) REFERENCES analytics_cycle_analytics_project_value_streams(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY software_license_policies
|
||||
ADD CONSTRAINT fk_rails_7a7a2a92de FOREIGN KEY (software_license_id) REFERENCES software_licenses(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -196,6 +196,65 @@ NOTE:
|
|||
The maximum import file size can be set by the Administrator, default is `0` (unlimited)..
|
||||
As an administrator, you can modify the maximum import file size. To do so, use the `max_import_size` option in the [Application settings API](settings.md#change-application-settings) or the [Admin UI](../user/admin_area/settings/account_and_limit_settings.md). Default [modified](https://gitlab.com/gitlab-org/gitlab/-/issues/251106) from 50MB to 0 in GitLab 13.8.
|
||||
|
||||
## Import a file from a remote object storage
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/282503) in GitLab 13.12 in [Beta](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta).
|
||||
|
||||
This endpoint is behind a feature flag that is disabled by default.
|
||||
|
||||
To enable this endpoint:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:import_project_from_remote_file)
|
||||
```
|
||||
|
||||
To disable this endpoint:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:import_project_from_remote_file)
|
||||
```
|
||||
|
||||
```plaintext
|
||||
POST /projects/remote-import
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ----------------- | -------------- | -------- | ---------------------------------------- |
|
||||
| `namespace` | integer/string | no | The ID or path of the namespace to import the project to. Defaults to the current user's namespace. |
|
||||
| `name` | string | no | The name of the project to import. If not provided, defaults to the path of the project. |
|
||||
| `url` | string | yes | URL for the file to import. |
|
||||
| `path` | string | yes | Name and path for the new project. |
|
||||
| `overwrite` | boolean | no | Whether to overwrite a project with the same path when importing. Defaults to `false`. |
|
||||
| `override_params` | Hash | no | Supports all fields defined in the [Project API](projects.md). |
|
||||
|
||||
The passed override parameters take precedence over all values defined in the export file.
|
||||
|
||||
```shell
|
||||
curl --request POST \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--url "https://gitlab.example.com/api/v4/projects/remote-import" \
|
||||
--data '{"url":"https://remoteobject/file?token=123123","path":"remote-project"}'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"description": null,
|
||||
"name": "remote-project",
|
||||
"name_with_namespace": "Administrator / remote-project",
|
||||
"path": "remote-project",
|
||||
"path_with_namespace": "root/remote-project",
|
||||
"created_at": "2018-02-13T09:05:58.023Z",
|
||||
"import_status": "scheduled",
|
||||
"correlation_id": "mezklWso3Za",
|
||||
"failed_relations": [],
|
||||
"import_error": null
|
||||
}
|
||||
```
|
||||
|
||||
The `ContentType` header must return a valid number. The maximum file size is 10 gigabytes.
|
||||
The `ContentLength` header must be `application/gzip`.
|
||||
|
||||
## Import status
|
||||
|
||||
Get the status of an import.
|
||||
|
|
|
|||
|
|
@ -795,6 +795,10 @@ aren't in the message with ID `1 pipeline`.
|
|||
|
||||
## Adding a new language
|
||||
|
||||
A new language should only be added as an option in User Preferences once at least 10% of the
|
||||
strings have been translated and approved. Even though a larger number of strings may have been
|
||||
translated, only the approved translations display in the GitLab UI.
|
||||
|
||||
NOTE:
|
||||
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221012) in GitLab 13.3:
|
||||
Languages with less than 2% of translations are not available in the UI.
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ using [Seat Link](#seat-link).
|
|||
A _billable user_ counts against the number of subscription seats. Every user is considered a
|
||||
billable user, with the following exceptions:
|
||||
|
||||
- [Deactivated users](../../user/admin_area/activating_deactivating_users.md#deactivating-a-user) and
|
||||
[blocked users](../../user/admin_area/blocking_unblocking_users.md) don't count as billable users in the current subscription. When they are either deactivated or blocked they release a _billable user_ seat. However, they may
|
||||
- [Deactivated users](../../user/admin_area/moderate_users.md#deactivating-a-user) and
|
||||
[blocked users](../../user/admin_area/moderate_users.md#blocking-a-user) don't count as billable users in the current subscription. When they are either deactivated or blocked they release a _billable user_ seat. However, they may
|
||||
count toward overages in the subscribed seat count.
|
||||
- Users who are [pending approval](../../user/admin_area/approving_users.md).
|
||||
- Members with Guest permissions on an Ultimate subscription.
|
||||
|
|
@ -183,7 +183,7 @@ Starting 30 days before a subscription expires, GitLab notifies administrators o
|
|||
|
||||
We recommend following these steps during renewal:
|
||||
|
||||
1. Prune any inactive or unwanted users by [blocking them](../../user/admin_area/blocking_unblocking_users.md#blocking-a-user).
|
||||
1. Prune any inactive or unwanted users by [blocking them](../../user/admin_area/moderate_users.md#blocking-a-user).
|
||||
1. Determine if you have a need for user growth in the upcoming subscription.
|
||||
1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in) and select the **Renew** button beneath your existing subscription.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,69 +1,8 @@
|
|||
---
|
||||
stage: Manage
|
||||
group: Access
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
type: howto
|
||||
redirect_to: 'moderate_users.md'
|
||||
---
|
||||
|
||||
# Activating and deactivating users
|
||||
This document was moved to [another location](moderate_users.md).
|
||||
|
||||
GitLab administrators can deactivate and activate users.
|
||||
|
||||
## Deactivating a user
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22257) in GitLab 12.4.
|
||||
|
||||
In order to temporarily prevent access by a GitLab user that has no recent activity, administrators
|
||||
can choose to deactivate the user.
|
||||
|
||||
Deactivating a user is functionally identical to [blocking a user](blocking_unblocking_users.md),
|
||||
with the following differences:
|
||||
|
||||
- It does not prohibit the user from logging back in via the UI.
|
||||
- Once a deactivated user logs back into the GitLab UI, their account is set to active.
|
||||
|
||||
A deactivated user:
|
||||
|
||||
- Cannot access Git repositories or the API.
|
||||
- Will not receive any notifications from GitLab.
|
||||
- Will not be able to use [slash commands](../../integration/slash_commands.md).
|
||||
|
||||
Personal projects, and group and user history of the deactivated user will be left intact.
|
||||
|
||||
A user can be deactivated from the Admin Area. To do this:
|
||||
|
||||
1. Navigate to **Admin Area > Overview > Users**.
|
||||
1. Select a user.
|
||||
1. Under the **Account** tab, click **Deactivate user**.
|
||||
|
||||
Please note that for the deactivation option to be visible to an admin, the user:
|
||||
|
||||
- Must be currently active.
|
||||
- Must not have signed in, or have any activity, in the last 90 days.
|
||||
|
||||
Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user).
|
||||
|
||||
NOTE:
|
||||
A deactivated user does not consume a [seat](../../subscriptions/self_managed/index.md#billable-users).
|
||||
|
||||
## Activating a user
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22257) in GitLab 12.4.
|
||||
|
||||
A deactivated user can be activated from the Admin Area.
|
||||
|
||||
To do this:
|
||||
|
||||
1. Navigate to **Admin Area > Overview > Users**.
|
||||
1. Click on the **Deactivated** tab.
|
||||
1. Select a user.
|
||||
1. Under the **Account** tab, click **Activate user**.
|
||||
|
||||
Users can also be activated using the [GitLab API](../../api/users.md#activate-user).
|
||||
|
||||
NOTE:
|
||||
Activating a user changes the user's state to active and consumes a
|
||||
[seat](../../subscriptions/self_managed/index.md#billable-users).
|
||||
|
||||
NOTE:
|
||||
A deactivated user can also activate their account themselves by logging back in via the UI.
|
||||
<!-- This redirect file can be deleted after <2021-08-12>. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ When a user registers for an account while this setting is enabled:
|
|||
|
||||
A user pending approval:
|
||||
|
||||
- Is functionally identical to a [blocked](blocking_unblocking_users.md) user.
|
||||
- Is functionally identical to a [blocked](moderate_users.md#blocking-a-user) user.
|
||||
- Cannot sign in.
|
||||
- Cannot access Git repositories or the GitLab API.
|
||||
- Does not receive any notifications from GitLab.
|
||||
|
|
|
|||
|
|
@ -1,51 +1,8 @@
|
|||
---
|
||||
stage: Manage
|
||||
group: Access
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
type: howto
|
||||
redirect_to: 'moderate_users.md'
|
||||
---
|
||||
|
||||
# Blocking and unblocking users
|
||||
This document was moved to [another location](moderate_users.md).
|
||||
|
||||
GitLab administrators block and unblock users.
|
||||
|
||||
## Blocking a user
|
||||
|
||||
In order to completely prevent access of a user to the GitLab instance, administrators can choose to
|
||||
block the user.
|
||||
|
||||
Users can be blocked [via an abuse report](review_abuse_reports.md#blocking-users),
|
||||
or directly from the Admin Area. To do this:
|
||||
|
||||
1. Navigate to **Admin Area > Overview > Users**.
|
||||
1. Select a user.
|
||||
1. Under the **Account** tab, click **Block user**.
|
||||
|
||||
A blocked user:
|
||||
|
||||
- Cannot log in.
|
||||
- Cannot access Git repositories or the API.
|
||||
- Does not receive any notifications from GitLab.
|
||||
- Cannot use [slash commands](../../integration/slash_commands.md).
|
||||
|
||||
Personal projects, and group and user history of the blocked user are left intact.
|
||||
|
||||
Users can also be blocked using the [GitLab API](../../api/users.md#block-user).
|
||||
|
||||
NOTE:
|
||||
A blocked user does not consume a [seat](../../subscriptions/self_managed/index.md#billable-users).
|
||||
|
||||
## Unblocking a user
|
||||
|
||||
A blocked user can be unblocked from the Admin Area. To do this:
|
||||
|
||||
1. Navigate to **Admin Area > Overview > Users**.
|
||||
1. Click on the **Blocked** tab.
|
||||
1. Select a user.
|
||||
1. Under the **Account** tab, click **Unblock user**.
|
||||
|
||||
Users can also be unblocked using the [GitLab API](../../api/users.md#unblock-user).
|
||||
|
||||
NOTE:
|
||||
Unblocking a user changes the user's state to active and consumes a
|
||||
[seat](../../subscriptions/self_managed/index.md#billable-users).
|
||||
<!-- This redirect file can be deleted after <2021-08-12>. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
|
||||
|
|
|
|||
|
|
@ -117,8 +117,8 @@ To list users matching a specific criteria, click on one of the following tabs o
|
|||
- **2FA Enabled**
|
||||
- **2FA Disabled**
|
||||
- **External**
|
||||
- **[Blocked](blocking_unblocking_users.md)**
|
||||
- **[Deactivated](activating_deactivating_users.md)**
|
||||
- **[Blocked](moderate_users.md#blocking-a-user)**
|
||||
- **[Deactivated](moderate_users.md#deactivating-a-user)**
|
||||
- **Without projects**
|
||||
|
||||
For each user, the following are listed:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
---
|
||||
stage: Manage
|
||||
group: Access
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
type: howto
|
||||
---
|
||||
|
||||
# Moderate users
|
||||
|
||||
GitLab administrators can moderate user access by blocking, banning, or deactivating users.
|
||||
|
||||
## Blocking and unblocking users
|
||||
|
||||
GitLab administrators can block and unblock users.
|
||||
|
||||
### Blocking a user
|
||||
|
||||
In order to completely prevent access of a user to the GitLab instance,
|
||||
administrators can choose to block the user.
|
||||
|
||||
Users can be blocked [via an abuse report](review_abuse_reports.md#blocking-users),
|
||||
or directly from the Admin Area. To do this:
|
||||
|
||||
1. Navigate to **Admin Area > Overview > Users**.
|
||||
1. Select a user.
|
||||
1. Under the **Account** tab, click **Block user**.
|
||||
|
||||
A blocked user:
|
||||
|
||||
- Cannot log in.
|
||||
- Cannot access Git repositories or the API.
|
||||
- Does not receive any notifications from GitLab.
|
||||
- Cannot use [slash commands](../../integration/slash_commands.md).
|
||||
|
||||
Personal projects, and group and user history of the blocked user are left intact.
|
||||
|
||||
Users can also be blocked using the [GitLab API](../../api/users.md#block-user).
|
||||
|
||||
NOTE:
|
||||
A blocked user does not consume a [seat](../../subscriptions/self_managed/index.md#billable-users).
|
||||
|
||||
### Unblocking a user
|
||||
|
||||
A blocked user can be unblocked from the Admin Area. To do this:
|
||||
|
||||
1. Navigate to **Admin Area > Overview > Users**.
|
||||
1. Click on the **Blocked** tab.
|
||||
1. Select a user.
|
||||
1. Under the **Account** tab, click **Unblock user**.
|
||||
|
||||
Users can also be unblocked using the [GitLab API](../../api/users.md#unblock-user).
|
||||
|
||||
NOTE:
|
||||
Unblocking a user changes the user's state to active and consumes a
|
||||
[seat](../../subscriptions/self_managed/index.md#billable-users).
|
||||
|
||||
## Activating and deactivating users
|
||||
|
||||
GitLab administrators can deactivate and activate users.
|
||||
|
||||
### Deactivating a user
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22257) in GitLab 12.4.
|
||||
|
||||
In order to temporarily prevent access by a GitLab user that has no recent activity,
|
||||
administrators can choose to deactivate the user.
|
||||
|
||||
Deactivating a user is functionally identical to [blocking a user](#blocking-and-unblocking-users),
|
||||
with the following differences:
|
||||
|
||||
- It does not prohibit the user from logging back in via the UI.
|
||||
- Once a deactivated user logs back into the GitLab UI, their account is set to active.
|
||||
|
||||
A deactivated user:
|
||||
|
||||
- Cannot access Git repositories or the API.
|
||||
- Will not receive any notifications from GitLab.
|
||||
- Will not be able to use [slash commands](../../integration/slash_commands.md).
|
||||
|
||||
Personal projects, and group and user history of the deactivated user are left intact.
|
||||
|
||||
A user can be deactivated from the Admin Area. To do this:
|
||||
|
||||
1. Navigate to **Admin Area > Overview > Users**.
|
||||
1. Select a user.
|
||||
1. Under the **Account** tab, click **Deactivate user**.
|
||||
|
||||
Please note that for the deactivation option to be visible to an admin, the user:
|
||||
|
||||
- Must be currently active.
|
||||
- Must not have signed in, or have any activity, in the last 90 days.
|
||||
|
||||
Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user).
|
||||
|
||||
NOTE:
|
||||
A deactivated user does not consume a [seat](../../subscriptions/self_managed/index.md#billable-users).
|
||||
|
||||
### Activating a user
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22257) in GitLab 12.4.
|
||||
|
||||
A deactivated user can be activated from the Admin Area.
|
||||
|
||||
To do this:
|
||||
|
||||
1. Navigate to **Admin Area > Overview > Users**.
|
||||
1. Click on the **Deactivated** tab.
|
||||
1. Select a user.
|
||||
1. Under the **Account** tab, click **Activate user**.
|
||||
|
||||
Users can also be activated using the [GitLab API](../../api/users.md#activate-user).
|
||||
|
||||
NOTE:
|
||||
Activating a user changes the user's state to active and consumes a
|
||||
[seat](../../subscriptions/self_managed/index.md#billable-users).
|
||||
|
||||
NOTE:
|
||||
A deactivated user can also activate their account themselves by logging back in via the UI.
|
||||
|
||||
## Ban and unban users
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327353) in GitLab 13.12.
|
||||
|
||||
GitLab administrators can ban users.
|
||||
|
||||
NOTE:
|
||||
This feature is behind a feature flag that is disabled by default. GitLab administrators
|
||||
with access to the GitLab Rails console can [enable](../../administration/feature_flags.md)
|
||||
this feature for your GitLab instance.
|
||||
|
||||
### Ban a user
|
||||
|
||||
To completely block a user, administrators can choose to ban the user.
|
||||
|
||||
Users can be banned using the Admin Area. To do this:
|
||||
|
||||
1. Navigate to **Admin Area > Overview > Users**.
|
||||
1. Select a user.
|
||||
1. Under the **Account** tab, click **Ban user**.
|
||||
|
||||
NOTE:
|
||||
This feature is a work in progress. Currently, banning a user
|
||||
only blocks them and does not hide their comments or issues.
|
||||
This functionality will be implemented in follow up issues.
|
||||
|
||||
### Unban a user
|
||||
|
||||
A banned user can be unbanned using the Admin Area. To do this:
|
||||
|
||||
1. Navigate to **Admin Area > Overview > Users**.
|
||||
1. Click on the **Banned** tab.
|
||||
1. Select a user.
|
||||
1. Under the **Account** tab, click **Unban user**.
|
||||
|
||||
NOTE:
|
||||
Unbanning a user changes the user's state to active and consumes a
|
||||
[seat](../../subscriptions/self_managed/index.md#billable-users).
|
||||
|
|
@ -501,29 +501,38 @@ For details on saving and transporting Docker images as a file, see Docker's doc
|
|||
|
||||
#### Automating container scanning vulnerability database updates with a pipeline
|
||||
|
||||
For those using Clair, it can be worthwhile to set up a [scheduled pipeline](../../../ci/pipelines/schedules.md)
|
||||
to build a new version of the vulnerabilities database on a preset schedule. Automating
|
||||
this with a pipeline means you do not have to do it manually each time. You can use the following
|
||||
`.gitlab-yml.ci` as a template:
|
||||
We recommend that you set up a [scheduled pipeline](../../../ci/pipelines/schedules.md)
|
||||
to fetch the latest vulnerabilities database on a preset schedule. Because the Clair scanner is
|
||||
deprecated, the latest vulnerabilities are currently only available for the Trivy scanner.
|
||||
Automating this with a pipeline means you do not have to do it manually each time. You can use the
|
||||
following `.gitlab-yml.ci` example as a template.
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
# If using Clair, uncomment the following 2 lines and comment the Trivy lines below
|
||||
# SOURCE_IMAGE: arminc/clair-db:latest
|
||||
# TARGET_IMAGE: $CI_REGISTRY/$CI_PROJECT_PATH/clair-vulnerabilities-db
|
||||
|
||||
# If using Trivy, uncomment the following 3 lines and comment the Clair lines above
|
||||
CS_MAJOR_VERSION: 4 # ensure that this value matches the one you use in your scanning jobs
|
||||
SOURCE_IMAGE: registry.gitlab.com/gitlab-org/security-products/analyzers/container-scanning:$CS_MAJOR_VERSION
|
||||
TARGET_IMAGE: $CI_REGISTRY/$CI_PROJECT_PATH/gitlab-container-scanning
|
||||
|
||||
image: docker:stable
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
build_latest_vulnerabilities:
|
||||
stage: build
|
||||
update-vulnerabilities-db:
|
||||
services:
|
||||
- docker:19.03.12-dind
|
||||
- docker:19-dind
|
||||
script:
|
||||
- docker pull arminc/clair-db:latest
|
||||
- docker tag arminc/clair-db:latest $CI_REGISTRY/namespace/clair-vulnerabilities-db
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||
- docker push $CI_REGISTRY/namespace/clair-vulnerabilities-db
|
||||
- docker pull $SOURCE_IMAGE
|
||||
- docker tag $SOURCE_IMAGE $TARGET_IMAGE
|
||||
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY --username $CI_REGISTRY_USER --password-stdin
|
||||
- docker push $TARGET_IMAGE
|
||||
```
|
||||
|
||||
The above template works for a GitLab Docker registry running on a local installation, however, if you're using a non-GitLab Docker registry, you need to change the `$CI_REGISTRY` value and the `docker login` credentials to match the details of your local registry.
|
||||
The above template works for a GitLab Docker registry running on a local installation. However, if
|
||||
you're using a non-GitLab Docker registry, you must change the `$CI_REGISTRY` value and the
|
||||
`docker login` credentials to match your local registry's details.
|
||||
|
||||
## Running the standalone container scanning tool
|
||||
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ The following vulnerability scanners and their databases are regularly updated:
|
|||
|
||||
| Secure scanning tool | Vulnerabilities database updates |
|
||||
|:----------------------------------------------------------------|----------------------------------|
|
||||
| [Container Scanning](../container_scanning/index.md) | Uses `clair`. The latest `clair-db` version is used for each job by running the [`latest` Docker image tag](https://gitlab.com/gitlab-org/gitlab/blob/438a0a56dc0882f22bdd82e700554525f552d91b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml#L37). The `clair-db` database [is updated daily according to the author](https://github.com/arminc/clair-local-scan#clair-server-or-local). |
|
||||
| [Container Scanning](../container_scanning/index.md) | Uses either `trivy` or `clair`. For the `trivy` scanner, a job runs on a daily basis to build a new image with the latest vulnerability database updates from the [upstream `trivy-db`](https://github.com/aquasecurity/trivy-db). For the `clair` scanner, the latest `clair-db` version is used; `clair-db` database [is updated daily according to the author](https://github.com/arminc/clair-local-scan#clair-server-or-local). |
|
||||
| [Dependency Scanning](../dependency_scanning/index.md) | Relies on `bundler-audit` (for Ruby gems), `retire.js` (for npm packages), and `gemnasium` (the GitLab tool for all libraries). Both `bundler-audit` and `retire.js` fetch their vulnerabilities data from GitHub repositories, so vulnerabilities added to `ruby-advisory-db` and `retire.js` are immediately available. The tools themselves are updated once per month if there's a new version. The [Gemnasium DB](https://gitlab.com/gitlab-org/security-products/gemnasium-db) is updated at least once a week. See our [current measurement of time from CVE being issued to our product being updated](https://about.gitlab.com/handbook/engineering/development/performance-indicators/#cve-issue-to-update). |
|
||||
| [Dynamic Application Security Testing (DAST)](../dast/index.md) | The scanning engine is updated on a periodic basis. See the [version of the underlying tool `zaproxy`](https://gitlab.com/gitlab-org/security-products/dast/blob/master/Dockerfile#L1). The scanning rules are downloaded at scan runtime. |
|
||||
| [Static Application Security Testing (SAST)](../sast/index.md) | Relies exclusively on [the tools GitLab wraps](../sast/index.md#supported-languages-and-frameworks). The underlying analyzers are updated at least once per month if a relevant update is available. The vulnerabilities database is updated by the upstream tools. |
|
||||
|
|
|
|||
|
|
@ -698,6 +698,13 @@ You can, however, remove the Container Registry for a project:
|
|||
|
||||
The **Packages & Registries > Container Registry** entry is removed from the project's sidebar.
|
||||
|
||||
## Manifest lists and garbage collection
|
||||
|
||||
Manifest lists are commonly used for creating multi-architecture images. If you rely on manifest
|
||||
lists, you should tag all the individual manifests referenced by a list in their respective
|
||||
repositories, and not just the manifest list itself. This ensures that those manifests aren't
|
||||
garbage collected, as long as they have at least one tag pointing to them.
|
||||
|
||||
## Troubleshooting the GitLab Container Registry
|
||||
|
||||
### Docker connection error
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ username of the original user.
|
|||
When using the **Delete user and contributions** option, **all** associated records
|
||||
are removed. This includes all of the items mentioned above including issues,
|
||||
merge requests, notes/comments, and more. Consider
|
||||
[blocking a user](../../admin_area/blocking_unblocking_users.md)
|
||||
[blocking a user](../../admin_area/moderate_users.md#blocking-a-user)
|
||||
or using the **Delete user** option instead.
|
||||
|
||||
When a user is deleted from an [abuse report](../../admin_area/review_abuse_reports.md)
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ Your account has been blocked. Fatal: Could not read from remote repository
|
|||
Your primary email address is not confirmed.
|
||||
```
|
||||
|
||||
You can assure your users that they have not been [Blocked](admin_area/blocking_unblocking_users.md) by an administrator.
|
||||
You can assure your users that they have not been [Blocked](admin_area/moderate_users.md#blocking-and-unblocking-users) by an administrator.
|
||||
When affected users see this message, they must confirm their email address before they can commit code.
|
||||
|
||||
## What do I need to know as an administrator of a GitLab self-managed Instance?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module Projects
|
||||
module Menus
|
||||
class DeploymentsMenu < ::Sidebars::Menu
|
||||
override :configure_menu_items
|
||||
def configure_menu_items
|
||||
return false if Feature.disabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml)
|
||||
|
||||
add_item(feature_flags_menu_item)
|
||||
add_item(environments_menu_item)
|
||||
add_item(releases_menu_item)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
override :link
|
||||
def link
|
||||
renderable_items.first.link
|
||||
end
|
||||
|
||||
override :extra_container_html_options
|
||||
def extra_container_html_options
|
||||
{
|
||||
class: 'shortcuts-deployments'
|
||||
}
|
||||
end
|
||||
|
||||
override :title
|
||||
def title
|
||||
_('Deployments')
|
||||
end
|
||||
|
||||
override :sprite_icon
|
||||
def sprite_icon
|
||||
'environment'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def feature_flags_menu_item
|
||||
unless can?(context.current_user, :read_feature_flag, context.project)
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :feature_flags)
|
||||
end
|
||||
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Feature Flags'),
|
||||
link: project_feature_flags_path(context.project),
|
||||
active_routes: { controller: :feature_flags },
|
||||
container_html_options: { class: 'shortcuts-feature-flags' },
|
||||
item_id: :feature_flags
|
||||
)
|
||||
end
|
||||
|
||||
def environments_menu_item
|
||||
unless can?(context.current_user, :read_environment, context.project)
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :environments)
|
||||
end
|
||||
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Environments'),
|
||||
link: project_environments_path(context.project),
|
||||
active_routes: { controller: :environments },
|
||||
container_html_options: { class: 'shortcuts-environments' },
|
||||
item_id: :environments
|
||||
)
|
||||
end
|
||||
|
||||
def releases_menu_item
|
||||
if !can?(context.current_user, :read_release, context.project) ||
|
||||
context.project.empty_repo?
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :releases)
|
||||
end
|
||||
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Releases'),
|
||||
link: project_releases_path(context.project),
|
||||
item_id: :releases,
|
||||
active_routes: { controller: :releases },
|
||||
container_html_options: { class: 'shortcuts-deployments-releases' }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -196,7 +196,8 @@ module Sidebars
|
|||
end
|
||||
|
||||
def environments_menu_item
|
||||
unless can?(context.current_user, :read_environment, context.project)
|
||||
if Feature.enabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) ||
|
||||
!can?(context.current_user, :read_environment, context.project)
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :environments)
|
||||
end
|
||||
|
||||
|
|
@ -210,7 +211,8 @@ module Sidebars
|
|||
end
|
||||
|
||||
def feature_flags_menu_item
|
||||
unless can?(context.current_user, :read_feature_flag, context.project)
|
||||
if Feature.enabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) ||
|
||||
!can?(context.current_user, :read_feature_flag, context.project)
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :feature_flags)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -84,9 +84,7 @@ module Sidebars
|
|||
end
|
||||
|
||||
def releases_menu_item
|
||||
if !can?(context.current_user, :read_release, context.project) || context.project.empty_repo?
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :releases)
|
||||
end
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :releases) unless show_releases?
|
||||
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Releases'),
|
||||
|
|
@ -97,6 +95,12 @@ module Sidebars
|
|||
)
|
||||
end
|
||||
|
||||
def show_releases?
|
||||
Feature.disabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) &&
|
||||
can?(context.current_user, :read_release, context.project) &&
|
||||
!context.project.empty_repo?
|
||||
end
|
||||
|
||||
def labels_menu_item
|
||||
if Feature.disabled?(:sidebar_refactor, context.current_user)
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :labels)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,17 @@ module Sidebars
|
|||
def configure_menus
|
||||
set_scope_menu(Sidebars::Projects::Menus::ScopeMenu.new(context))
|
||||
set_hidden_menu(Sidebars::Projects::Menus::HiddenMenu.new(context))
|
||||
add_menus
|
||||
end
|
||||
|
||||
override :aria_label
|
||||
def aria_label
|
||||
_('Project navigation')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_menus
|
||||
add_menu(Sidebars::Projects::Menus::ProjectInformationMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::LearnGitlabMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::RepositoryMenu.new(context))
|
||||
|
|
@ -17,6 +27,7 @@ module Sidebars
|
|||
add_menu(Sidebars::Projects::Menus::MergeRequestsMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::CiCdMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::SecurityComplianceMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::DeploymentsMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::OperationsMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::InfrastructureMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::PackagesRegistriesMenu.new(context))
|
||||
|
|
@ -28,13 +39,6 @@ module Sidebars
|
|||
add_menu(Sidebars::Projects::Menus::SettingsMenu.new(context))
|
||||
end
|
||||
|
||||
override :aria_label
|
||||
def aria_label
|
||||
_('Project navigation')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def confluence_or_wiki_menu
|
||||
confluence_menu = ::Sidebars::Projects::Menus::ConfluenceMenu.new(context)
|
||||
|
||||
|
|
|
|||
|
|
@ -1375,6 +1375,9 @@ msgstr ""
|
|||
msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates."
|
||||
msgstr ""
|
||||
|
||||
msgid "A banned user cannot:"
|
||||
msgstr ""
|
||||
|
||||
msgid "A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2437,6 +2440,9 @@ msgstr ""
|
|||
msgid "AdminUsers|(Admin)"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|(Banned)"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|(Blocked)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2503,6 +2509,21 @@ msgstr ""
|
|||
msgid "AdminUsers|Automatically marked as default internal user"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Ban"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Ban user"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Ban user %{username}?"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Banned"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Banning the user has the following effects:"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Be added to groups and projects"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2590,6 +2611,9 @@ msgstr ""
|
|||
msgid "AdminUsers|It's you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Learn more about %{link_start}banned users.%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Log in"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2671,6 +2695,15 @@ msgstr ""
|
|||
msgid "AdminUsers|To confirm, type %{username}"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Unban"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Unban %{username}?"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Unban user"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|Unblock"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2686,6 +2719,9 @@ msgstr ""
|
|||
msgid "AdminUsers|Unlock user %{username}?"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|User will be blocked"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|User will not be able to access git repositories"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2722,6 +2758,9 @@ msgstr ""
|
|||
msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strongStart}block user%{strongEnd} feature instead. Once you %{strongStart}Delete user%{strongEnd}, it cannot be undone or recovered."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|You ban their account in the future if necessary."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|You can always block their account again if needed."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2734,6 +2773,9 @@ msgstr ""
|
|||
msgid "AdminUsers|You can always unblock their account, their data will remain intact."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|You can unban their account in the future. Their data remains intact."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminUsers|You cannot remove your own admin rights."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -4985,6 +5027,9 @@ msgstr ""
|
|||
msgid "BillingPlans|@%{user_name} you are currently using the %{plan_name}."
|
||||
msgstr ""
|
||||
|
||||
msgid "BillingPlans|Compare all plans"
|
||||
msgstr ""
|
||||
|
||||
msgid "BillingPlans|Congratulations, your free trial is activated."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -5018,6 +5063,9 @@ msgstr ""
|
|||
msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
|
||||
msgstr ""
|
||||
|
||||
msgid "BillingPlans|Upgrade to GitLab %{planNameForUpgrade}"
|
||||
msgstr ""
|
||||
|
||||
msgid "BillingPlans|While GitLab is ending availability of the Bronze plan, you can still renew your Bronze subscription one additional time before %{eoa_bronze_plan_end_date}. We are also offering a limited time free upgrade to our Premium Plan (up to 25 users)! Learn more about the changes and offers in our %{announcement_link}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -12898,12 +12946,18 @@ msgstr ""
|
|||
msgid "Error occurred. A blocked user must be unblocked to be activated"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error occurred. User was not banned"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error occurred. User was not blocked"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error occurred. User was not confirmed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error occurred. User was not unbanned"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error occurred. User was not unblocked"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31249,6 +31303,9 @@ msgstr ""
|
|||
msgid "Successfully approved"
|
||||
msgstr ""
|
||||
|
||||
msgid "Successfully banned"
|
||||
msgstr ""
|
||||
|
||||
msgid "Successfully blocked"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31273,6 +31330,9 @@ msgstr ""
|
|||
msgid "Successfully synced %{synced_timeago}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Successfully unbanned"
|
||||
msgstr ""
|
||||
|
||||
msgid "Successfully unblocked"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33382,6 +33442,9 @@ msgstr ""
|
|||
msgid "This user has the %{access} role in the %{name} project."
|
||||
msgstr ""
|
||||
|
||||
msgid "This user is banned"
|
||||
msgstr ""
|
||||
|
||||
msgid "This user is blocked"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,9 @@
|
|||
|
||||
export SETUP_DB=${SETUP_DB:-true}
|
||||
export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
|
||||
export BUNDLE_INSTALL_FLAGS=${BUNDLE_INSTALL_FLAGS:-"--without=production development --jobs=$(nproc) --path=vendor --retry=3 --quiet"}
|
||||
|
||||
if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
|
||||
bundle --version
|
||||
bundle config set clean 'true'
|
||||
run_timed_command "bundle install ${BUNDLE_INSTALL_FLAGS}"
|
||||
run_timed_command "bundle check"
|
||||
# When we test multiple versions of PG in the same pipeline, we have a single `setup-test-env`
|
||||
# job but the `pg` gem needs to be rebuilt since it includes extensions (https://guides.rubygems.org/gems-with-extensions).
|
||||
# Uncomment the following line if multiple versions of PG are tested in the same pipeline.
|
||||
run_timed_command "bundle pristine pg"
|
||||
bundle_install_script
|
||||
fi
|
||||
|
||||
cp config/gitlab.yml.example config/gitlab.yml
|
||||
|
|
|
|||
|
|
@ -13,6 +13,32 @@ function retry() {
|
|||
return 1
|
||||
}
|
||||
|
||||
function bundle_install_script() {
|
||||
local extra_install_args="${1}"
|
||||
|
||||
if [[ "${extra_install_args}" =~ "--without" ]]; then
|
||||
echoerr "The '--without' flag shouldn't be passed as it would replace the default \${BUNDLE_WITHOUT} (currently set to '${BUNDLE_WITHOUT}')."
|
||||
echoerr "Set the 'BUNDLE_WITHOUT' variable instead, e.g. '- export BUNDLE_WITHOUT=\"\${BUNDLE_WITHOUT}:any:other:group:not:to:install\"'."
|
||||
exit 1;
|
||||
fi;
|
||||
|
||||
bundle --version
|
||||
bundle config set path 'vendor'
|
||||
bundle config set clean 'true'
|
||||
|
||||
echo $BUNDLE_WITHOUT
|
||||
bundle config
|
||||
|
||||
run_timed_command "bundle install ${BUNDLE_INSTALL_FLAGS} ${extra_install_args} && bundle check"
|
||||
|
||||
if [[ $(bundle info pg) ]]; then
|
||||
# When we test multiple versions of PG in the same pipeline, we have a single `setup-test-env`
|
||||
# job but the `pg` gem needs to be rebuilt since it includes extensions (https://guides.rubygems.org/gems-with-extensions).
|
||||
# Uncomment the following line if multiple versions of PG are tested in the same pipeline.
|
||||
run_timed_command "bundle pristine pg"
|
||||
fi
|
||||
}
|
||||
|
||||
function setup_db_user_only() {
|
||||
source scripts/create_postgres_user.sh
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,17 @@ RSpec.describe Admin::RunnersController do
|
|||
expect(response.body).to have_content('tag1')
|
||||
expect(response.body).to have_content('tag2')
|
||||
end
|
||||
|
||||
it 'paginates runners' do
|
||||
stub_const("Admin::RunnersController::NUMBER_OF_RUNNERS_PER_PAGE", 1)
|
||||
|
||||
create(:ci_runner)
|
||||
|
||||
get :index
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(assigns(:runners).count).to be(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#show' do
|
||||
|
|
|
|||
|
|
@ -365,6 +365,56 @@ RSpec.describe Admin::UsersController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'PUT ban/:id' do
|
||||
context 'when ban_user_feature_flag is enabled' do
|
||||
it 'bans user' do
|
||||
put :ban, params: { id: user.username }
|
||||
|
||||
user.reload
|
||||
expect(user.banned?).to be_truthy
|
||||
expect(flash[:notice]).to eq _('Successfully banned')
|
||||
end
|
||||
|
||||
context 'when unsuccessful' do
|
||||
let(:user) { create(:user, :blocked) }
|
||||
|
||||
it 'does not ban user' do
|
||||
put :ban, params: { id: user.username }
|
||||
|
||||
user.reload
|
||||
expect(user.banned?).to be_falsey
|
||||
expect(flash[:alert]).to eq _('Error occurred. User was not banned')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ban_user_feature_flag is not enabled' do
|
||||
before do
|
||||
stub_feature_flags(ban_user_feature_flag: false)
|
||||
end
|
||||
|
||||
it 'does not ban user, renders 404' do
|
||||
put :ban, params: { id: user.username }
|
||||
|
||||
user.reload
|
||||
expect(user.banned?).to be_falsey
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT unban/:id' do
|
||||
let(:banned_user) { create(:user, :banned) }
|
||||
|
||||
it 'unbans user' do
|
||||
put :unban, params: { id: banned_user.username }
|
||||
|
||||
banned_user.reload
|
||||
expect(banned_user.banned?).to be_falsey
|
||||
expect(flash[:notice]).to eq _('Successfully unbanned')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT unlock/:id' do
|
||||
before do
|
||||
request.env["HTTP_REFERER"] = "/"
|
||||
|
|
|
|||
|
|
@ -32,6 +32,17 @@ RSpec.describe Groups::Settings::CiCdController do
|
|||
expect(response).to render_template(:show)
|
||||
expect(assigns(:group_runners)).to match_array([runner_group, runner_project_1, runner_project_2, runner_project_3])
|
||||
end
|
||||
|
||||
it 'paginates runners' do
|
||||
stub_const("Groups::Settings::CiCdController::NUMBER_OF_RUNNERS_PER_PAGE", 1)
|
||||
|
||||
create(:ci_runner)
|
||||
|
||||
get :show, params: { group_id: group }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(assigns(:group_runners).count).to be(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not owner' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::Analytics::CycleAnalytics::StagesController do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
|
||||
let(:params) { { namespace_id: group, project_id: project, value_stream_id: 'default' } }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
describe 'GET index' do
|
||||
context 'when user is member of the project' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'succeeds' do
|
||||
get :index, params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'exposes the default stages' do
|
||||
get :index, params: params
|
||||
|
||||
expect(json_response['stages'].size).to eq(Gitlab::Analytics::CycleAnalytics::DefaultStages.all.size)
|
||||
end
|
||||
|
||||
context 'when list service fails' do
|
||||
it 'renders 403' do
|
||||
expect_next_instance_of(Analytics::CycleAnalytics::Stages::ListService) do |list_service|
|
||||
expect(list_service).to receive(:allowed?).and_return(false)
|
||||
end
|
||||
|
||||
get :index, params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid value stream id is given' do
|
||||
before do
|
||||
params[:value_stream_id] = 1
|
||||
end
|
||||
|
||||
it 'renders 404' do
|
||||
get :index, params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not member of the project' do
|
||||
it 'renders 404' do
|
||||
get :index, params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::Analytics::CycleAnalytics::ValueStreamsController do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
|
||||
let(:params) { { namespace_id: group, project_id: project } }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
describe 'GET index' do
|
||||
context 'when user is member of the project' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'succeeds' do
|
||||
get :index, params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'exposes the default value stream' do
|
||||
get :index, params: params
|
||||
|
||||
expect(json_response.first['name']).to eq('default')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not member of the project' do
|
||||
it 'renders 404' do
|
||||
get :index, params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,6 +6,7 @@ FactoryBot.define do
|
|||
sequence(:name) { |n| "Stage ##{n}" }
|
||||
hidden { false }
|
||||
issue_stage
|
||||
value_stream { association(:cycle_analytics_project_value_stream, project: project) }
|
||||
|
||||
trait :issue_stage do
|
||||
start_event_identifier { Gitlab::Analytics::CycleAnalytics::StageEvents::IssueCreated.identifier }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :cycle_analytics_project_value_stream, class: 'Analytics::CycleAnalytics::ProjectValueStream' do
|
||||
sequence(:name) { |n| "Value Stream ##{n}" }
|
||||
|
||||
project
|
||||
end
|
||||
end
|
||||
|
|
@ -27,6 +27,10 @@ FactoryBot.define do
|
|||
after(:build) { |user, _| user.block_pending_approval! }
|
||||
end
|
||||
|
||||
trait :banned do
|
||||
after(:build) { |user, _| user.ban! }
|
||||
end
|
||||
|
||||
trait :ldap_blocked do
|
||||
after(:build) { |user, _| user.ldap_block! }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ RSpec.describe 'Admin::Users' do
|
|||
expect(page).to have_link('2FA Disabled', href: admin_users_path(filter: 'two_factor_disabled'))
|
||||
expect(page).to have_link('External', href: admin_users_path(filter: 'external'))
|
||||
expect(page).to have_link('Blocked', href: admin_users_path(filter: 'blocked'))
|
||||
expect(page).to have_link('Banned', href: admin_users_path(filter: 'banned'))
|
||||
expect(page).to have_link('Deactivated', href: admin_users_path(filter: 'deactivated'))
|
||||
expect(page).to have_link('Without projects', href: admin_users_path(filter: 'wop'))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -91,8 +91,6 @@ RSpec.describe 'Project navbar' do
|
|||
_('Error Tracking'),
|
||||
_('Alerts'),
|
||||
_('Incidents'),
|
||||
_('Environments'),
|
||||
_('Feature Flags'),
|
||||
_('Product Analytics')
|
||||
]
|
||||
end
|
||||
|
|
@ -102,7 +100,6 @@ RSpec.describe 'Project navbar' do
|
|||
nav_item: _('Project information'),
|
||||
nav_sub_items: [
|
||||
_('Activity'),
|
||||
_('Releases'),
|
||||
_('Labels')
|
||||
]
|
||||
}
|
||||
|
|
@ -133,6 +130,18 @@ RSpec.describe 'Project navbar' do
|
|||
}
|
||||
)
|
||||
|
||||
insert_after_nav_item(
|
||||
_('Security & Compliance'),
|
||||
new_nav_item: {
|
||||
nav_item: _('Deployments'),
|
||||
nav_sub_items: [
|
||||
_('Feature Flags'),
|
||||
_('Environments'),
|
||||
_('Releases')
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
visit project_path(project)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -178,6 +178,16 @@ RSpec.describe 'User uses shortcuts', :js do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when navigating to the Deployments page' do
|
||||
it 'redirects to the Environments page' do
|
||||
find('body').native.send_key('g')
|
||||
find('body').native.send_key('e')
|
||||
|
||||
expect(page).to have_active_navigation('Deployments')
|
||||
expect(page).to have_active_sub_navigation('Environments')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when navigating to the Operations pages' do
|
||||
it 'redirects to the Metrics page' do
|
||||
find('body').native.send_key('g')
|
||||
|
|
@ -187,24 +197,26 @@ RSpec.describe 'User uses shortcuts', :js do
|
|||
expect(page).to have_active_sub_navigation('Metrics')
|
||||
end
|
||||
|
||||
it 'redirects to the Environments page' do
|
||||
find('body').native.send_key('g')
|
||||
find('body').native.send_key('e')
|
||||
|
||||
expect(page).to have_active_navigation('Operations')
|
||||
expect(page).to have_active_sub_navigation('Environments')
|
||||
end
|
||||
|
||||
context 'when feature flag :sidebar_refactor is disabled' do
|
||||
it 'redirects to the Kubernetes page with active Operations' do
|
||||
before do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
end
|
||||
|
||||
it 'redirects to the Kubernetes page with active Operations' do
|
||||
find('body').native.send_key('g')
|
||||
find('body').native.send_key('k')
|
||||
|
||||
expect(page).to have_active_navigation('Operations')
|
||||
expect(page).to have_active_sub_navigation('Kubernetes')
|
||||
end
|
||||
|
||||
it 'redirects to the Environments page' do
|
||||
find('body').native.send_key('g')
|
||||
find('body').native.send_key('e')
|
||||
|
||||
expect(page).to have_active_navigation('Operations')
|
||||
expect(page).to have_active_sub_navigation('Environments')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Analytics::CycleAnalytics::StageFinder do
|
||||
let(:project) { build(:project) }
|
||||
|
||||
let(:stage_id) { { id: Gitlab::Analytics::CycleAnalytics::DefaultStages.names.first } }
|
||||
|
||||
subject { described_class.new(parent: project, stage_id: stage_id[:id]).execute }
|
||||
|
||||
context 'when looking up in-memory default stage by name exists' do
|
||||
it { expect(subject).not_to be_persisted }
|
||||
it { expect(subject.name).to eq(stage_id[:id]) }
|
||||
end
|
||||
|
||||
context 'when in-memory default stage cannot be found' do
|
||||
before do
|
||||
stage_id[:id] = 'unknown_default_stage'
|
||||
end
|
||||
|
||||
it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
|
||||
end
|
||||
end
|
||||
|
|
@ -72,17 +72,6 @@ RSpec.describe Ci::RunnersFinder do
|
|||
end
|
||||
end
|
||||
|
||||
context 'paginate' do
|
||||
it 'returns the runners for the specified page' do
|
||||
stub_const('Ci::RunnersFinder::NUMBER_OF_RUNNERS_PER_PAGE', 1)
|
||||
runner1 = create :ci_runner, created_at: '2018-07-12 07:00'
|
||||
runner2 = create :ci_runner, created_at: '2018-07-12 08:00'
|
||||
|
||||
expect(described_class.new(current_user: admin, params: { page: 1 }).execute).to eq [runner2]
|
||||
expect(described_class.new(current_user: admin, params: { page: 2 }).execute).to eq [runner1]
|
||||
end
|
||||
end
|
||||
|
||||
context 'non admin user' do
|
||||
it 'returns no runners' do
|
||||
user = create :user
|
||||
|
|
@ -172,38 +161,6 @@ RSpec.describe Ci::RunnersFinder do
|
|||
end
|
||||
end
|
||||
|
||||
context 'paginate' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:runners) do
|
||||
[[runner_project_7, runner_project_6, runner_project_5],
|
||||
[runner_project_4, runner_project_3, runner_project_2],
|
||||
[runner_project_1, runner_sub_group_4, runner_sub_group_3],
|
||||
[runner_sub_group_2, runner_sub_group_1, runner_group]]
|
||||
end
|
||||
|
||||
where(:page, :index) do
|
||||
1 | 0
|
||||
2 | 1
|
||||
3 | 2
|
||||
4 | 3
|
||||
end
|
||||
|
||||
before do
|
||||
stub_const('Ci::RunnersFinder::NUMBER_OF_RUNNERS_PER_PAGE', 3)
|
||||
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:params) { { page: page } }
|
||||
|
||||
it 'returns the runners for the specified page' do
|
||||
expect(subject).to eq(runners[index])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'filter by search term' do
|
||||
let(:params) { { search: 'runner_project_search' } }
|
||||
|
||||
|
|
|
|||
|
|
@ -136,6 +136,16 @@ RSpec.describe UsersHelper do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a banned user' do
|
||||
it 'returns the banned badge' do
|
||||
banned_user = create(:user, :banned)
|
||||
|
||||
badges = helper.user_badges_in_admin_section(banned_user)
|
||||
|
||||
expect(filter_ee_badges(badges)).to eq([text: 'Banned', variant: 'danger'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an admin user' do
|
||||
it "returns the admin badge" do
|
||||
admin_user = create(:admin)
|
||||
|
|
|
|||
|
|
@ -352,6 +352,7 @@ project:
|
|||
- cluster_project
|
||||
- creator
|
||||
- cycle_analytics_stages
|
||||
- value_streams
|
||||
- group
|
||||
- namespace
|
||||
- management_clusters
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Sidebars::Projects::Menus::DeploymentsMenu do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
let(:user) { project.owner }
|
||||
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) }
|
||||
|
||||
describe '#render?' do
|
||||
subject { described_class.new(context) }
|
||||
|
||||
context 'when menu does not have any menu items' do
|
||||
it 'returns false' do
|
||||
allow(subject).to receive(:has_renderable_items?).and_return(false)
|
||||
|
||||
expect(subject.render?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when menu has menu items' do
|
||||
it 'returns true' do
|
||||
expect(subject.render?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Menu Items' do
|
||||
subject { described_class.new(context).renderable_items.index { |e| e.item_id == item_id } }
|
||||
|
||||
shared_examples 'access rights checks' do
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'feature flag :sidebar_refactor disabled' do
|
||||
before do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
end
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
describe 'Feature Flags' do
|
||||
let(:item_id) { :feature_flags }
|
||||
|
||||
it_behaves_like 'access rights checks'
|
||||
it_behaves_like 'feature flag :sidebar_refactor disabled'
|
||||
end
|
||||
|
||||
describe 'Environments' do
|
||||
let(:item_id) { :environments }
|
||||
|
||||
it_behaves_like 'access rights checks'
|
||||
it_behaves_like 'feature flag :sidebar_refactor disabled'
|
||||
end
|
||||
|
||||
describe 'Releases' do
|
||||
let(:item_id) { :releases }
|
||||
|
||||
it_behaves_like 'access rights checks'
|
||||
it_behaves_like 'feature flag :sidebar_refactor disabled'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -56,9 +56,7 @@ RSpec.describe Sidebars::Projects::Menus::OperationsMenu do
|
|||
context 'Menu items' do
|
||||
subject { described_class.new(context).renderable_items.index { |e| e.item_id == item_id } }
|
||||
|
||||
describe 'Metrics Dashboard' do
|
||||
let(:item_id) { :metrics }
|
||||
|
||||
shared_examples 'access rights checks' do
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
|
|
@ -68,153 +66,109 @@ RSpec.describe Sidebars::Projects::Menus::OperationsMenu do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'Metrics Dashboard' do
|
||||
let(:item_id) { :metrics }
|
||||
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
|
||||
describe 'Logs' do
|
||||
let(:item_id) { :logs }
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
|
||||
describe 'Tracing' do
|
||||
let(:item_id) { :tracing }
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
|
||||
describe 'Error Tracking' do
|
||||
let(:item_id) { :error_tracking }
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
|
||||
describe 'Alert Management' do
|
||||
let(:item_id) { :alert_management }
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
|
||||
describe 'Incidents' do
|
||||
let(:item_id) { :incidents }
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
|
||||
describe 'Serverless' do
|
||||
let(:item_id) { :serverless }
|
||||
|
||||
context 'when feature flag :sidebar_refactor is enabled' do
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
specify { is_expected.to be_nil }
|
||||
|
||||
context 'when feature flag :sidebar_refactor is disabled' do
|
||||
before do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
end
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Terraform' do
|
||||
let(:item_id) { :terraform }
|
||||
|
||||
context 'when feature flag :sidebar_refactor is enabled' do
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
specify { is_expected.to be_nil }
|
||||
|
||||
context 'when feature flag :sidebar_refactor is disabled' do
|
||||
before do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
end
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Kubernetes' do
|
||||
let(:item_id) { :kubernetes }
|
||||
|
||||
context 'when feature flag :sidebar_refactor is enabled' do
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
specify { is_expected.to be_nil }
|
||||
|
||||
context 'when feature flag :sidebar_refactor is disabled' do
|
||||
before do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
end
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Environments' do
|
||||
let(:item_id) { :environments }
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
specify { is_expected.to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
context 'when feature flag :sidebar_refactor is disabled' do
|
||||
before do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
end
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Feature Flags' do
|
||||
let(:item_id) { :feature_flags }
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
specify { is_expected.to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
context 'when feature flag :sidebar_refactor is disabled' do
|
||||
before do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
end
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -14,26 +14,30 @@ RSpec.describe Sidebars::Projects::Menus::ProjectInformationMenu do
|
|||
describe 'Releases' do
|
||||
let(:item_id) { :releases }
|
||||
|
||||
context 'when project repository is empty' do
|
||||
it 'does not include releases menu item' do
|
||||
allow(project).to receive(:empty_repo?).and_return(true)
|
||||
specify { is_expected.to be_nil }
|
||||
|
||||
is_expected.to be_nil
|
||||
context 'when feature flag :sidebar_refactor is disabled' do
|
||||
before do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project repository is not empty' do
|
||||
context 'when user can download code' do
|
||||
it 'includes releases menu item' do
|
||||
is_expected.to be_present
|
||||
context 'when project repository is empty' do
|
||||
it 'does not include releases menu item' do
|
||||
allow(project).to receive(:empty_repo?).and_return(true)
|
||||
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user cannot download code' do
|
||||
let(:user) { nil }
|
||||
context 'when project repository is not empty' do
|
||||
context 'when user can download code' do
|
||||
specify { is_expected.not_to be_nil }
|
||||
end
|
||||
|
||||
it 'does not include releases menu item' do
|
||||
is_expected.to be_nil
|
||||
context 'when user cannot download code' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require Rails.root.join('db', 'migrate', '20210503105845_add_project_value_stream_id_to_project_stages.rb')
|
||||
|
||||
RSpec.describe AddProjectValueStreamIdToProjectStages, schema: 20210503105022 do
|
||||
let(:stages) { table(:analytics_cycle_analytics_project_stages) }
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
let(:projects) { table(:projects) }
|
||||
|
||||
let(:namespace) { table(:namespaces).create!(name: 'ns1', path: 'nsq1') }
|
||||
|
||||
before do
|
||||
project = projects.create!(name: 'p1', namespace_id: namespace.id)
|
||||
|
||||
stages.create!(
|
||||
project_id: project.id,
|
||||
created_at: Time.now,
|
||||
updated_at: Time.now,
|
||||
start_event_identifier: 1,
|
||||
end_event_identifier: 2,
|
||||
name: 'stage 1'
|
||||
)
|
||||
|
||||
stages.create!(
|
||||
project_id: project.id,
|
||||
created_at: Time.now,
|
||||
updated_at: Time.now,
|
||||
start_event_identifier: 3,
|
||||
end_event_identifier: 4,
|
||||
name: 'stage 2'
|
||||
)
|
||||
end
|
||||
|
||||
it 'deletes the existing rows' do
|
||||
migrate!
|
||||
|
||||
expect(stages.count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
|
@ -17,6 +17,7 @@ RSpec.describe Analytics::CycleAnalytics::ProjectStage do
|
|||
end
|
||||
|
||||
it_behaves_like 'value stream analytics stage' do
|
||||
let(:factory) { :cycle_analytics_project_stage }
|
||||
let(:parent) { build(:project) }
|
||||
let(:parent_name) { :project }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Analytics::CycleAnalytics::ProjectValueStream, type: :model do
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:project) }
|
||||
it { is_expected.to have_many(:stages) }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:project) }
|
||||
it { is_expected.to validate_presence_of(:name) }
|
||||
it { is_expected.to validate_length_of(:name).is_at_most(100) }
|
||||
|
||||
it 'validates uniqueness of name' do
|
||||
project = create(:project)
|
||||
create(:cycle_analytics_project_value_stream, name: 'test', project: project)
|
||||
|
||||
value_stream = build(:cycle_analytics_project_value_stream, name: 'test', project: project)
|
||||
|
||||
expect(value_stream).to be_invalid
|
||||
expect(value_stream.errors.messages).to eq(name: [I18n.t('errors.messages.taken')])
|
||||
end
|
||||
end
|
||||
|
||||
it 'is not custom' do
|
||||
expect(described_class.new).not_to be_custom
|
||||
end
|
||||
|
||||
describe '.build_default_value_stream' do
|
||||
it 'builds the default value stream' do
|
||||
project = build(:project)
|
||||
|
||||
value_stream = described_class.build_default_value_stream(project)
|
||||
expect(value_stream.name).to eq('default')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -113,7 +113,8 @@ RSpec.describe Project, factory_default: :keep do
|
|||
it { is_expected.to have_many(:lfs_file_locks) }
|
||||
it { is_expected.to have_many(:project_deploy_tokens) }
|
||||
it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
|
||||
it { is_expected.to have_many(:cycle_analytics_stages) }
|
||||
it { is_expected.to have_many(:cycle_analytics_stages).inverse_of(:project) }
|
||||
it { is_expected.to have_many(:value_streams).inverse_of(:project) }
|
||||
it { is_expected.to have_many(:external_pull_requests) }
|
||||
it { is_expected.to have_many(:sourced_pipelines) }
|
||||
it { is_expected.to have_many(:source_pipelines) }
|
||||
|
|
|
|||
|
|
@ -728,6 +728,7 @@ RSpec.describe User do
|
|||
let_it_be(:blocked_user) { create(:user, :blocked) }
|
||||
let_it_be(:ldap_blocked_user) { create(:omniauth_user, :ldap_blocked) }
|
||||
let_it_be(:blocked_pending_approval_user) { create(:user, :blocked_pending_approval) }
|
||||
let_it_be(:banned_user) { create(:user, :banned) }
|
||||
|
||||
describe '.blocked' do
|
||||
subject { described_class.blocked }
|
||||
|
|
@ -738,7 +739,7 @@ RSpec.describe User do
|
|||
ldap_blocked_user
|
||||
)
|
||||
|
||||
expect(subject).not_to include(active_user, blocked_pending_approval_user)
|
||||
expect(subject).not_to include(active_user, blocked_pending_approval_user, banned_user)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -749,6 +750,14 @@ RSpec.describe User do
|
|||
expect(subject).to contain_exactly(blocked_pending_approval_user)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.banned' do
|
||||
subject { described_class.banned }
|
||||
|
||||
it 'returns only banned users' do
|
||||
expect(subject).to contain_exactly(banned_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".with_two_factor" do
|
||||
|
|
@ -1934,6 +1943,12 @@ RSpec.describe User do
|
|||
expect(described_class.filter_items('blocked')).to include user
|
||||
end
|
||||
|
||||
it 'filters by banned' do
|
||||
expect(described_class).to receive(:banned).and_return([user])
|
||||
|
||||
expect(described_class.filter_items('banned')).to include user
|
||||
end
|
||||
|
||||
it 'filters by blocked pending approval' do
|
||||
expect(described_class).to receive(:blocked_pending_approval).and_return([user])
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Analytics::CycleAnalytics::StageEntity do
|
||||
let(:stage) { build(:cycle_analytics_project_stage, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) }
|
||||
|
||||
subject(:entity_json) { described_class.new(Analytics::CycleAnalytics::StagePresenter.new(stage)).as_json }
|
||||
|
||||
it 'exposes start and end event descriptions' do
|
||||
expect(entity_json).to have_key(:start_event_html_description)
|
||||
expect(entity_json).to have_key(:end_event_html_description)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Analytics::CycleAnalytics::Stages::ListService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:value_stream) { Analytics::CycleAnalytics::ProjectValueStream.build_default_value_stream(project) }
|
||||
let(:stages) { subject.payload[:stages] }
|
||||
|
||||
subject { described_class.new(parent: project, current_user: user).execute }
|
||||
|
||||
before_all do
|
||||
project.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'returns only the default stages' do
|
||||
expect(stages.size).to eq(Gitlab::Analytics::CycleAnalytics::DefaultStages.all.size)
|
||||
end
|
||||
|
||||
it 'provides the default stages as non-persisted objects' do
|
||||
expect(stages.map(&:id)).to all(be_nil)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Users::BanService do
|
||||
let(:current_user) { create(:admin) }
|
||||
|
||||
subject(:service) { described_class.new(current_user) }
|
||||
|
||||
describe '#execute' do
|
||||
subject(:operation) { service.execute(user) }
|
||||
|
||||
context 'when successful' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it { is_expected.to eq(status: :success) }
|
||||
|
||||
it "bans the user" do
|
||||
expect { operation }.to change { user.state }.to('banned')
|
||||
end
|
||||
|
||||
it "blocks the user" do
|
||||
expect { operation }.to change { user.blocked? }.from(false).to(true)
|
||||
end
|
||||
|
||||
it 'logs ban in application logs' do
|
||||
allow(Gitlab::AppLogger).to receive(:info)
|
||||
|
||||
operation
|
||||
|
||||
expect(Gitlab::AppLogger).to have_received(:info).with(message: "User banned", user: "#{user.username}", email: "#{user.email}", banned_by: "#{current_user.username}", ip_address: "#{current_user.current_sign_in_ip}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed' do
|
||||
let(:user) { create(:user, :blocked) }
|
||||
|
||||
it 'returns error result' do
|
||||
aggregate_failures 'error result' do
|
||||
expect(operation[:status]).to eq(:error)
|
||||
expect(operation[:message]).to match(/State cannot transition/)
|
||||
end
|
||||
end
|
||||
|
||||
it "does not change the user's state" do
|
||||
expect { operation }.not_to change { user.state }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -58,6 +58,19 @@ RSpec.shared_examples 'value stream analytics stage' do
|
|||
|
||||
it { expect(stage).not_to be_valid }
|
||||
end
|
||||
|
||||
# rubocop: disable Rails/SaveBang
|
||||
describe '.by_value_stream' do
|
||||
it 'finds stages by value stream' do
|
||||
stage1 = create(factory)
|
||||
create(factory) # other stage with different value stream
|
||||
|
||||
result = described_class.by_value_stream(stage1.value_stream)
|
||||
|
||||
expect(result).to eq([stage1])
|
||||
end
|
||||
end
|
||||
# rubocop: enable Rails/SaveBang
|
||||
end
|
||||
|
||||
describe '#subject_class' do
|
||||
|
|
|
|||
|
|
@ -66,10 +66,20 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
|
|||
end
|
||||
|
||||
describe 'Releases' do
|
||||
it 'has a link to the project releases path' do
|
||||
it 'does not have a link to the project releases path' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Releases', href: project_releases_path(project), class: 'shortcuts-project-releases')
|
||||
expect(rendered).not_to have_link('Releases', href: project_releases_path(project), class: 'shortcuts-project-releases')
|
||||
end
|
||||
|
||||
context 'when feature flag :sidebar refactor is disabled' do
|
||||
it 'has a link to the project releases path' do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Releases', href: project_releases_path(project), class: 'shortcuts-project-releases')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -417,6 +427,86 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'Deployments' do
|
||||
let(:page) { Nokogiri::HTML.parse(rendered) }
|
||||
|
||||
describe 'Feature Flags' do
|
||||
it 'has a link to the feature flags page' do
|
||||
render
|
||||
|
||||
expect(page.at_css('.shortcuts-deployments').parent.css('[aria-label="Feature Flags"]')).not_to be_empty
|
||||
expect(rendered).to have_link('Feature Flags', href: project_feature_flags_path(project))
|
||||
end
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'does not have a link to the feature flags page' do
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_link('Feature Flags')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag :sidebar_refactor is disabled' do
|
||||
it 'does not have a Feature Flags menu item' do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_selector('.shortcuts-deployments')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Environments' do
|
||||
it 'has a link to the environments page' do
|
||||
render
|
||||
|
||||
expect(page.at_css('.shortcuts-deployments').parent.css('[aria-label="Environments"]')).not_to be_empty
|
||||
expect(rendered).to have_link('Environments', href: project_environments_path(project))
|
||||
end
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'does not have a link to the environments page' do
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_link('Environments')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag :sidebar_refactor is disabled' do
|
||||
it 'does not have a Environments menu item' do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_selector('.shortcuts-deployments')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Releases' do
|
||||
it 'has a link to the project releases path' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Releases', href: project_releases_path(project), class: 'shortcuts-deployments-releases')
|
||||
end
|
||||
|
||||
context 'when feature flag :sidebar refactor is disabled' do
|
||||
it 'does not have a link to the project releases path' do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_link('Releases', href: project_releases_path(project), class: 'shortcuts-deployments-releases')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Operations' do
|
||||
it 'top level navigation link is visible for user with permissions' do
|
||||
render
|
||||
|
|
@ -610,37 +700,67 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
|
|||
end
|
||||
|
||||
describe 'Environments' do
|
||||
it 'has a link to the environments page' do
|
||||
let(:page) { Nokogiri::HTML.parse(rendered) }
|
||||
|
||||
it 'does not have a link to the environments page' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Environments', href: project_environments_path(project))
|
||||
expect(page.at_css('.shortcuts-operations').parent.css('[aria-label="Environments"]')).to be_empty
|
||||
end
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
context 'when feature flag :sidebar_refactor is disabled' do
|
||||
before do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
end
|
||||
|
||||
it 'does not have a link to the environments page' do
|
||||
it 'has a link to the environments page' do
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_link('Environments')
|
||||
expect(page.at_css('.shortcuts-operations').parent.css('[aria-label="Environments"]')).not_to be_empty
|
||||
expect(rendered).to have_link('Environments', href: project_environments_path(project))
|
||||
end
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'does not have a link to the environments page' do
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_link('Environments')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Feature Flags' do
|
||||
it 'has a link to the feature flags page' do
|
||||
let(:page) { Nokogiri::HTML.parse(rendered) }
|
||||
|
||||
it 'does not have a link to the feature flags page' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Feature Flags', href: project_feature_flags_path(project))
|
||||
expect(page.at_css('.shortcuts-operations').parent.css('[aria-label="Feature Flags"]')).to be_empty
|
||||
end
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
context 'when feature flag :sidebar_refactor is disabled' do
|
||||
before do
|
||||
stub_feature_flags(sidebar_refactor: false)
|
||||
end
|
||||
|
||||
it 'does not have a link to the feature flags page' do
|
||||
it 'has a link to the feature flags page' do
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_link('Feature Flags')
|
||||
expect(page.at_css('.shortcuts-operations').parent.css('[aria-label="Feature Flags"]')).not_to be_empty
|
||||
expect(rendered).to have_link('Feature Flags', href: project_feature_flags_path(project))
|
||||
end
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'does not have a link to the feature flags page' do
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_link('Feature Flags')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -103,5 +103,14 @@ RSpec.describe Packages::Nuget::ExtractionWorker, type: :worker do
|
|||
it_behaves_like 'handling the metadata error'
|
||||
end
|
||||
end
|
||||
|
||||
context 'handles a processing an unaccounted for error' do
|
||||
before do
|
||||
expect(::Packages::Nuget::UpdatePackageFromMetadataService).to receive(:new)
|
||||
.and_raise(Zip::Error)
|
||||
end
|
||||
|
||||
it_behaves_like 'handling the metadata error', exception_class: Zip::Error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -37,6 +37,20 @@ RSpec.describe Packages::Rubygems::ExtractionWorker, type: :worker do
|
|||
expect(package.reload).to be_error
|
||||
end
|
||||
|
||||
it 'handles processing an unaccounted for error', :aggregate_failures do
|
||||
expect(::Packages::Rubygems::ProcessGemService).to receive(:new)
|
||||
.and_raise(Zip::Error)
|
||||
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
|
||||
instance_of(Zip::Error),
|
||||
project_id: package.project_id
|
||||
)
|
||||
|
||||
subject
|
||||
|
||||
expect(package.reload).to be_error
|
||||
end
|
||||
|
||||
context 'returns when there is no package file' do
|
||||
let(:package_file_id) { 999999 }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue