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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-14 21:10:34 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-14 21:10:34 +0300
commit7d4b2ed7bf75d316577b718c71a9fdef19184539 (patch)
treed709e00c4f2ab60901749883f324f9069343037c
parent7172fb10313a9a7790f8e033b347e77df4987154 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml8
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml3
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml4
-rw-r--r--app/assets/javascripts/feature_flags/components/feature_flags.vue5
-rw-r--r--app/assets/javascripts/feature_flags/index.js4
-rw-r--r--app/controllers/admin/runners_controller.rb4
-rw-r--r--app/controllers/admin/users_controller.rb23
-rw-r--r--app/controllers/projects/analytics/cycle_analytics/stages_controller.rb44
-rw-r--r--app/controllers/projects/analytics/cycle_analytics/value_streams_controller.rb16
-rw-r--r--app/finders/analytics/cycle_analytics/stage_finder.rb37
-rw-r--r--app/finders/ci/runners_finder.rb7
-rw-r--r--app/helpers/users_helper.rb46
-rw-r--r--app/models/analytics/cycle_analytics/project_stage.rb3
-rw-r--r--app/models/analytics/cycle_analytics/project_value_stream.rb22
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stage.rb1
-rw-r--r--app/models/project.rb3
-rw-r--r--app/models/user.rb11
-rw-r--r--app/serializers/analytics/cycle_analytics/configuration_entity.rb22
-rw-r--r--app/serializers/analytics/cycle_analytics/event_entity.rb37
-rw-r--r--app/serializers/analytics/cycle_analytics/stage_entity.rb38
-rw-r--r--app/serializers/analytics/cycle_analytics/value_stream_entity.rb24
-rw-r--r--app/serializers/analytics/cycle_analytics/value_stream_serializer.rb9
-rw-r--r--app/services/analytics/cycle_analytics/stages/base_service.rb47
-rw-r--r--app/services/analytics/cycle_analytics/stages/list_service.rb27
-rw-r--r--app/services/users/ban_service.rb25
-rw-r--r--app/views/admin/users/_ban_user.html.haml9
-rw-r--r--app/views/admin/users/_head.html.haml3
-rw-r--r--app/views/admin/users/_users.html.haml5
-rw-r--r--app/views/admin/users/show.html.haml15
-rw-r--r--app/workers/packages/nuget/extraction_worker.rb3
-rw-r--r--app/workers/packages/rubygems/extraction_worker.rb2
-rw-r--r--changelogs/unreleased/324206-extraction-error-rescue.yml5
-rw-r--r--changelogs/unreleased/329208-project-level-value-stream.yml5
-rw-r--r--changelogs/unreleased/afontaine-inject-feature-flag-limit.yml5
-rw-r--r--changelogs/unreleased/ban-user-state-ui.yml5
-rw-r--r--config/feature_flags/development/ban_user_feature_flag.yml8
-rw-r--r--config/routes/admin.rb2
-rw-r--r--config/routes/project.rb9
-rw-r--r--db/migrate/20210503105022_create_project_value_streams.rb27
-rw-r--r--db/migrate/20210503105845_add_project_value_stream_id_to_project_stages.rb30
-rw-r--r--db/schema_migrations/202105031050221
-rw-r--r--db/schema_migrations/202105031058451
-rw-r--r--db/structure.sql36
-rw-r--r--doc/api/project_import_export.md59
-rw-r--r--doc/development/i18n/externalization.md4
-rw-r--r--doc/subscriptions/self_managed/index.md6
-rw-r--r--doc/user/admin_area/activating_deactivating_users.md69
-rw-r--r--doc/user/admin_area/approving_users.md2
-rw-r--r--doc/user/admin_area/blocking_unblocking_users.md51
-rw-r--r--doc/user/admin_area/index.md4
-rw-r--r--doc/user/admin_area/moderate_users.md157
-rw-r--r--doc/user/application_security/container_scanning/index.md39
-rw-r--r--doc/user/application_security/vulnerabilities/index.md2
-rw-r--r--doc/user/packages/container_registry/index.md7
-rw-r--r--doc/user/profile/account/delete_account.md2
-rw-r--r--doc/user/upgrade_email_bypass.md2
-rw-r--r--lib/sidebars/projects/menus/deployments_menu.rb87
-rw-r--r--lib/sidebars/projects/menus/operations_menu.rb6
-rw-r--r--lib/sidebars/projects/menus/project_information_menu.rb10
-rw-r--r--lib/sidebars/projects/panel.rb18
-rw-r--r--locale/gitlab.pot63
-rw-r--r--scripts/prepare_build.sh10
-rw-r--r--scripts/utils.sh26
-rw-r--r--spec/controllers/admin/runners_controller_spec.rb11
-rw-r--r--spec/controllers/admin/users_controller_spec.rb50
-rw-r--r--spec/controllers/groups/settings/ci_cd_controller_spec.rb11
-rw-r--r--spec/controllers/projects/analytics/cycle_analytics/stages_controller_spec.rb67
-rw-r--r--spec/controllers/projects/analytics/cycle_analytics/value_streams_controller_spec.rb43
-rw-r--r--spec/factories/analytics/cycle_analytics/project_stages.rb1
-rw-r--r--spec/factories/analytics/cycle_analytics/project_value_streams.rb9
-rw-r--r--spec/factories/users.rb4
-rw-r--r--spec/features/admin/users/users_spec.rb1
-rw-r--r--spec/features/projects/navbar_spec.rb15
-rw-r--r--spec/features/projects/user_uses_shortcuts_spec.rb30
-rw-r--r--spec/finders/analytics/cycle_analytics/stage_finder_spec.rb24
-rw-r--r--spec/finders/ci/runners_finder_spec.rb43
-rw-r--r--spec/helpers/users_helper_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/sidebars/projects/menus/deployments_menu_spec.rb71
-rw-r--r--spec/lib/sidebars/projects/menus/operations_menu_spec.rb106
-rw-r--r--spec/lib/sidebars/projects/menus/project_information_menu_spec.rb30
-rw-r--r--spec/migrations/20210503105845_add_project_value_stream_id_to_project_stages_spec.rb41
-rw-r--r--spec/models/analytics/cycle_analytics/project_stage_spec.rb1
-rw-r--r--spec/models/analytics/cycle_analytics/project_value_stream_spec.rb39
-rw-r--r--spec/models/project_spec.rb3
-rw-r--r--spec/models/user_spec.rb17
-rw-r--r--spec/serializers/analytics/cycle_analytics/stage_entity_spec.rb14
-rw-r--r--spec/services/analytics/cycle_analytics/stages/list_service_spec.rb25
-rw-r--r--spec/services/users/ban_service_spec.rb50
-rw-r--r--spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb13
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb148
-rw-r--r--spec/workers/packages/nuget/extraction_worker_spec.rb9
-rw-r--r--spec/workers/packages/rubygems/extraction_worker_spec.rb14
94 files changed, 1828 insertions, 340 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index bfc4e4f7316..c255d6d87fc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -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"
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 788b482f0a6..5097fd28eeb 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -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:
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index f534f341841..834ba040501 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -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:
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 78305a651d1..f87a5759836 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -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:
- >
diff --git a/app/assets/javascripts/feature_flags/components/feature_flags.vue b/app/assets/javascripts/feature_flags/components/feature_flags.vue
index 115d68de5c9..9aa1accb0f2 100644
--- a/app/assets/javascripts/feature_flags/components/feature_flags.vue
+++ b/app/assets/javascripts/feature_flags/components/feature_flags.vue
@@ -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;
diff --git a/app/assets/javascripts/feature_flags/index.js b/app/assets/javascripts/feature_flags/index.js
index a92805d5d85..d2371a2aa8b 100644
--- a/app/assets/javascripts/feature_flags/index.js
+++ b/app/assets/javascripts/feature_flags/index.js
@@ -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);
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 576b148fbff..40ec68c1d46 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -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
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 9f0d911f5ef..2e9229db56c 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -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
diff --git a/app/controllers/projects/analytics/cycle_analytics/stages_controller.rb b/app/controllers/projects/analytics/cycle_analytics/stages_controller.rb
new file mode 100644
index 00000000000..7b4f6739a9b
--- /dev/null
+++ b/app/controllers/projects/analytics/cycle_analytics/stages_controller.rb
@@ -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
diff --git a/app/controllers/projects/analytics/cycle_analytics/value_streams_controller.rb b/app/controllers/projects/analytics/cycle_analytics/value_streams_controller.rb
new file mode 100644
index 00000000000..03dcb164d94
--- /dev/null
+++ b/app/controllers/projects/analytics/cycle_analytics/value_streams_controller.rb
@@ -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
diff --git a/app/finders/analytics/cycle_analytics/stage_finder.rb b/app/finders/analytics/cycle_analytics/stage_finder.rb
new file mode 100644
index 00000000000..732e9ff3e00
--- /dev/null
+++ b/app/finders/analytics/cycle_analytics/stage_finder.rb
@@ -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')
diff --git a/app/finders/ci/runners_finder.rb b/app/finders/ci/runners_finder.rb
index 1b76211c524..60dd977ff94 100644
--- a/app/finders/ci/runners_finder.rb
+++ b/app/finders/ci/runners_finder.rb
@@ -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]
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 998c697e617..c1d05c2d3cf 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -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
diff --git a/app/models/analytics/cycle_analytics/project_stage.rb b/app/models/analytics/cycle_analytics/project_stage.rb
index b2c16444a2a..e8b03fa066a 100644
--- a/app/models/analytics/cycle_analytics/project_stage.rb
+++ b/app/models/analytics/cycle_analytics/project_stage.rb
@@ -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? }
diff --git a/app/models/analytics/cycle_analytics/project_value_stream.rb b/app/models/analytics/cycle_analytics/project_value_stream.rb
new file mode 100644
index 00000000000..3eba7e87b17
--- /dev/null
+++ b/app/models/analytics/cycle_analytics/project_value_stream.rb
@@ -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
diff --git a/app/models/concerns/analytics/cycle_analytics/stage.rb b/app/models/concerns/analytics/cycle_analytics/stage.rb
index f1c39dda49d..90d48aa81d0 100644
--- a/app/models/concerns/analytics/cycle_analytics/stage.rb
+++ b/app/models/concerns/analytics/cycle_analytics/stage.rb
@@ -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=(_)
diff --git a/app/models/project.rb b/app/models/project.rb
index 6d8f46d9ea6..759633a70d2 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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
diff --git a/app/models/user.rb b/app/models/user.rb
index 4b38ef771a1..4ff6221bcab 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -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'
diff --git a/app/serializers/analytics/cycle_analytics/configuration_entity.rb b/app/serializers/analytics/cycle_analytics/configuration_entity.rb
new file mode 100644
index 00000000000..45ea7c92758
--- /dev/null
+++ b/app/serializers/analytics/cycle_analytics/configuration_entity.rb
@@ -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
diff --git a/app/serializers/analytics/cycle_analytics/event_entity.rb b/app/serializers/analytics/cycle_analytics/event_entity.rb
new file mode 100644
index 00000000000..b9abf722c8d
--- /dev/null
+++ b/app/serializers/analytics/cycle_analytics/event_entity.rb
@@ -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
diff --git a/app/serializers/analytics/cycle_analytics/stage_entity.rb b/app/serializers/analytics/cycle_analytics/stage_entity.rb
new file mode 100644
index 00000000000..b24148802d0
--- /dev/null
+++ b/app/serializers/analytics/cycle_analytics/stage_entity.rb
@@ -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
diff --git a/app/serializers/analytics/cycle_analytics/value_stream_entity.rb b/app/serializers/analytics/cycle_analytics/value_stream_entity.rb
new file mode 100644
index 00000000000..1943efcc63d
--- /dev/null
+++ b/app/serializers/analytics/cycle_analytics/value_stream_entity.rb
@@ -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
diff --git a/app/serializers/analytics/cycle_analytics/value_stream_serializer.rb b/app/serializers/analytics/cycle_analytics/value_stream_serializer.rb
new file mode 100644
index 00000000000..ffd7aa882e4
--- /dev/null
+++ b/app/serializers/analytics/cycle_analytics/value_stream_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Analytics
+ module CycleAnalytics
+ class ValueStreamSerializer < BaseSerializer
+ entity ::Analytics::CycleAnalytics::ValueStreamEntity
+ end
+ end
+end
diff --git a/app/services/analytics/cycle_analytics/stages/base_service.rb b/app/services/analytics/cycle_analytics/stages/base_service.rb
new file mode 100644
index 00000000000..b676eff0a0b
--- /dev/null
+++ b/app/services/analytics/cycle_analytics/stages/base_service.rb
@@ -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')
diff --git a/app/services/analytics/cycle_analytics/stages/list_service.rb b/app/services/analytics/cycle_analytics/stages/list_service.rb
new file mode 100644
index 00000000000..a6b94ef8295
--- /dev/null
+++ b/app/services/analytics/cycle_analytics/stages/list_service.rb
@@ -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')
diff --git a/app/services/users/ban_service.rb b/app/services/users/ban_service.rb
new file mode 100644
index 00000000000..247ed14966b
--- /dev/null
+++ b/app/services/users/ban_service.rb
@@ -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
diff --git a/app/views/admin/users/_ban_user.html.haml b/app/views/admin/users/_ban_user.html.haml
new file mode 100644
index 00000000000..229c88adb7f
--- /dev/null
+++ b/app/views/admin/users/_ban_user.html.haml
@@ -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')
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index ade3581e5b9..be04e87f8b9 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -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)')
diff --git a/app/views/admin/users/_users.html.haml b/app/views/admin/users/_users.html.haml
index efa50ec47f5..e4438f38a47 100644
--- a/app/views/admin/users/_users.html.haml
+++ b/app/views/admin/users/_users.html.haml
@@ -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')
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 03ddf5c76a0..19cc29668f5 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -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
diff --git a/app/workers/packages/nuget/extraction_worker.rb b/app/workers/packages/nuget/extraction_worker.rb
index 97f900c4ff2..4128b229ebe 100644
--- a/app/workers/packages/nuget/extraction_worker.rb
+++ b/app/workers/packages/nuget/extraction_worker.rb
@@ -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
diff --git a/app/workers/packages/rubygems/extraction_worker.rb b/app/workers/packages/rubygems/extraction_worker.rb
index a5630e89140..fc32654a2c1 100644
--- a/app/workers/packages/rubygems/extraction_worker.rb
+++ b/app/workers/packages/rubygems/extraction_worker.rb
@@ -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
diff --git a/changelogs/unreleased/324206-extraction-error-rescue.yml b/changelogs/unreleased/324206-extraction-error-rescue.yml
new file mode 100644
index 00000000000..dcbf5400a88
--- /dev/null
+++ b/changelogs/unreleased/324206-extraction-error-rescue.yml
@@ -0,0 +1,5 @@
+---
+title: Log additional package extraction errors
+merge_request: 61745
+author:
+type: other
diff --git a/changelogs/unreleased/329208-project-level-value-stream.yml b/changelogs/unreleased/329208-project-level-value-stream.yml
new file mode 100644
index 00000000000..cc5e986e68d
--- /dev/null
+++ b/changelogs/unreleased/329208-project-level-value-stream.yml
@@ -0,0 +1,5 @@
+---
+title: Create database structure to support project value streams
+merge_request: 60925
+author:
+type: other
diff --git a/changelogs/unreleased/afontaine-inject-feature-flag-limit.yml b/changelogs/unreleased/afontaine-inject-feature-flag-limit.yml
new file mode 100644
index 00000000000..464cf91a8e1
--- /dev/null
+++ b/changelogs/unreleased/afontaine-inject-feature-flag-limit.yml
@@ -0,0 +1,5 @@
+---
+title: Inject Feature Flags Limit Value
+merge_request: 61621
+author:
+type: fixed
diff --git a/changelogs/unreleased/ban-user-state-ui.yml b/changelogs/unreleased/ban-user-state-ui.yml
new file mode 100644
index 00000000000..6da3d575dc9
--- /dev/null
+++ b/changelogs/unreleased/ban-user-state-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Ban user state and UI
+merge_request: 61292
+author:
+type: added
diff --git a/config/feature_flags/development/ban_user_feature_flag.yml b/config/feature_flags/development/ban_user_feature_flag.yml
new file mode 100644
index 00000000000..6765e82e252
--- /dev/null
+++ b/config/feature_flags/development/ban_user_feature_flag.yml
@@ -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
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index e7f851f7de4..2ba00e3bf66 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -21,6 +21,8 @@ namespace :admin do
get :keys
put :block
put :unblock
+ put :ban
+ put :unban
put :deactivate
put :activate
put :unlock
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 43b0e2a5568..d62e2f1b2f2 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -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
diff --git a/db/migrate/20210503105022_create_project_value_streams.rb b/db/migrate/20210503105022_create_project_value_streams.rb
new file mode 100644
index 00000000000..775ab03ad4b
--- /dev/null
+++ b/db/migrate/20210503105022_create_project_value_streams.rb
@@ -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
diff --git a/db/migrate/20210503105845_add_project_value_stream_id_to_project_stages.rb b/db/migrate/20210503105845_add_project_value_stream_id_to_project_stages.rb
new file mode 100644
index 00000000000..d888ab4943c
--- /dev/null
+++ b/db/migrate/20210503105845_add_project_value_stream_id_to_project_stages.rb
@@ -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
diff --git a/db/schema_migrations/20210503105022 b/db/schema_migrations/20210503105022
new file mode 100644
index 00000000000..ada5b2db7da
--- /dev/null
+++ b/db/schema_migrations/20210503105022
@@ -0,0 +1 @@
+de8bf6c02589bf308914d43e5cd44dae91d3bbabcdaafcebdb96fba0a09b20bc \ No newline at end of file
diff --git a/db/schema_migrations/20210503105845 b/db/schema_migrations/20210503105845
new file mode 100644
index 00000000000..ff2c910491b
--- /dev/null
+++ b/db/schema_migrations/20210503105845
@@ -0,0 +1 @@
+2fdcb66e511d8322ea8fc4de66ecce859f8e91b2a9da22336281a1e784d9b4a5 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 1337d16a635..f3e3840085b 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -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;
diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md
index b95f8c2984d..a4ad496b667 100644
--- a/doc/api/project_import_export.md
+++ b/doc/api/project_import_export.md
@@ -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.
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index f3d09903108..b177a7e0138 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -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.
diff --git a/doc/subscriptions/self_managed/index.md b/doc/subscriptions/self_managed/index.md
index db0d54ff14c..648ad0c70a5 100644
--- a/doc/subscriptions/self_managed/index.md
+++ b/doc/subscriptions/self_managed/index.md
@@ -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.
diff --git a/doc/user/admin_area/activating_deactivating_users.md b/doc/user/admin_area/activating_deactivating_users.md
index 144ee2dbf98..cafc7caf981 100644
--- a/doc/user/admin_area/activating_deactivating_users.md
+++ b/doc/user/admin_area/activating_deactivating_users.md
@@ -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 -->
diff --git a/doc/user/admin_area/approving_users.md b/doc/user/admin_area/approving_users.md
index 9141d7f488d..2b3b90cb1a4 100644
--- a/doc/user/admin_area/approving_users.md
+++ b/doc/user/admin_area/approving_users.md
@@ -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.
diff --git a/doc/user/admin_area/blocking_unblocking_users.md b/doc/user/admin_area/blocking_unblocking_users.md
index 14a5b5085cb..cafc7caf981 100644
--- a/doc/user/admin_area/blocking_unblocking_users.md
+++ b/doc/user/admin_area/blocking_unblocking_users.md
@@ -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 -->
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index b0a84d981c9..5d1fde1c767 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -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:
diff --git a/doc/user/admin_area/moderate_users.md b/doc/user/admin_area/moderate_users.md
new file mode 100644
index 00000000000..c04003dd75f
--- /dev/null
+++ b/doc/user/admin_area/moderate_users.md
@@ -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).
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 6848126e163..10d276bd224 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -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
-image: docker:stable
+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
-stages:
- - build
+ # 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
-build_latest_vulnerabilities:
- stage: build
+image: docker:stable
+
+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
diff --git a/doc/user/application_security/vulnerabilities/index.md b/doc/user/application_security/vulnerabilities/index.md
index b96451fa67d..965b856504d 100644
--- a/doc/user/application_security/vulnerabilities/index.md
+++ b/doc/user/application_security/vulnerabilities/index.md
@@ -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. |
diff --git a/doc/user/packages/container_registry/index.md b/doc/user/packages/container_registry/index.md
index 3b0c59ac31c..6d7b009bb09 100644
--- a/doc/user/packages/container_registry/index.md
+++ b/doc/user/packages/container_registry/index.md
@@ -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
diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md
index c0560269f00..361353a0f8c 100644
--- a/doc/user/profile/account/delete_account.md
+++ b/doc/user/profile/account/delete_account.md
@@ -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)
diff --git a/doc/user/upgrade_email_bypass.md b/doc/user/upgrade_email_bypass.md
index 199c1a47e04..7d2f2395815 100644
--- a/doc/user/upgrade_email_bypass.md
+++ b/doc/user/upgrade_email_bypass.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?
diff --git a/lib/sidebars/projects/menus/deployments_menu.rb b/lib/sidebars/projects/menus/deployments_menu.rb
new file mode 100644
index 00000000000..f3d13e12258
--- /dev/null
+++ b/lib/sidebars/projects/menus/deployments_menu.rb
@@ -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
diff --git a/lib/sidebars/projects/menus/operations_menu.rb b/lib/sidebars/projects/menus/operations_menu.rb
index 3503dcaa0c1..8d72a9ff258 100644
--- a/lib/sidebars/projects/menus/operations_menu.rb
+++ b/lib/sidebars/projects/menus/operations_menu.rb
@@ -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
diff --git a/lib/sidebars/projects/menus/project_information_menu.rb b/lib/sidebars/projects/menus/project_information_menu.rb
index 82b0971f747..80b3acecc2f 100644
--- a/lib/sidebars/projects/menus/project_information_menu.rb
+++ b/lib/sidebars/projects/menus/project_information_menu.rb
@@ -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)
diff --git a/lib/sidebars/projects/panel.rb b/lib/sidebars/projects/panel.rb
index 261d51626cc..3754d381dcc 100644
--- a/lib/sidebars/projects/panel.rb
+++ b/lib/sidebars/projects/panel.rb
@@ -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)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 01a5e40941a..f54303ad457 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index cd41aa0ff14..5753a0af4f8 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -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
diff --git a/scripts/utils.sh b/scripts/utils.sh
index 2e9839e4df8..d4436e1171d 100644
--- a/scripts/utils.sh
+++ b/scripts/utils.sh
@@ -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
}
diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb
index 45ea8949bf2..3984784f045 100644
--- a/spec/controllers/admin/runners_controller_spec.rb
+++ b/spec/controllers/admin/runners_controller_spec.rb
@@ -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
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 1afd20f5021..722c9c322cc 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -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"] = "/"
diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
index db16579c3e7..f225d798886 100644
--- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
@@ -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
diff --git a/spec/controllers/projects/analytics/cycle_analytics/stages_controller_spec.rb b/spec/controllers/projects/analytics/cycle_analytics/stages_controller_spec.rb
new file mode 100644
index 00000000000..3bb841c7c9f
--- /dev/null
+++ b/spec/controllers/projects/analytics/cycle_analytics/stages_controller_spec.rb
@@ -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
diff --git a/spec/controllers/projects/analytics/cycle_analytics/value_streams_controller_spec.rb b/spec/controllers/projects/analytics/cycle_analytics/value_streams_controller_spec.rb
new file mode 100644
index 00000000000..5b434eb2011
--- /dev/null
+++ b/spec/controllers/projects/analytics/cycle_analytics/value_streams_controller_spec.rb
@@ -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
diff --git a/spec/factories/analytics/cycle_analytics/project_stages.rb b/spec/factories/analytics/cycle_analytics/project_stages.rb
index 3a481bd20fd..e673c4957b0 100644
--- a/spec/factories/analytics/cycle_analytics/project_stages.rb
+++ b/spec/factories/analytics/cycle_analytics/project_stages.rb
@@ -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 }
diff --git a/spec/factories/analytics/cycle_analytics/project_value_streams.rb b/spec/factories/analytics/cycle_analytics/project_value_streams.rb
new file mode 100644
index 00000000000..45a6470b0aa
--- /dev/null
+++ b/spec/factories/analytics/cycle_analytics/project_value_streams.rb
@@ -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
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 7ac44d55687..476c57f2d80 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -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
diff --git a/spec/features/admin/users/users_spec.rb b/spec/features/admin/users/users_spec.rb
index 36907d4aa60..d3931373ee3 100644
--- a/spec/features/admin/users/users_spec.rb
+++ b/spec/features/admin/users/users_spec.rb
@@ -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
diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb
index 74f1c639af3..edd33f503fd 100644
--- a/spec/features/projects/navbar_spec.rb
+++ b/spec/features/projects/navbar_spec.rb
@@ -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
diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb
index cf97897d976..53466bc59d5 100644
--- a/spec/features/projects/user_uses_shortcuts_spec.rb
+++ b/spec/features/projects/user_uses_shortcuts_spec.rb
@@ -178,33 +178,45 @@ RSpec.describe 'User uses shortcuts', :js do
end
end
- context 'when navigating to the Operations pages' do
- it 'redirects to the Metrics page' do
+ 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('l')
+ find('body').native.send_key('e')
- expect(page).to have_active_navigation('Operations')
- expect(page).to have_active_sub_navigation('Metrics')
+ expect(page).to have_active_navigation('Deployments')
+ expect(page).to have_active_sub_navigation('Environments')
end
+ end
- it 'redirects to the Environments page' do
+ context 'when navigating to the Operations pages' do
+ it 'redirects to the Metrics page' do
find('body').native.send_key('g')
- find('body').native.send_key('e')
+ find('body').native.send_key('l')
expect(page).to have_active_navigation('Operations')
- expect(page).to have_active_sub_navigation('Environments')
+ expect(page).to have_active_sub_navigation('Metrics')
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
diff --git a/spec/finders/analytics/cycle_analytics/stage_finder_spec.rb b/spec/finders/analytics/cycle_analytics/stage_finder_spec.rb
new file mode 100644
index 00000000000..0275205028a
--- /dev/null
+++ b/spec/finders/analytics/cycle_analytics/stage_finder_spec.rb
@@ -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
diff --git a/spec/finders/ci/runners_finder_spec.rb b/spec/finders/ci/runners_finder_spec.rb
index 34639f9b7bd..4df026f2f5f 100644
--- a/spec/finders/ci/runners_finder_spec.rb
+++ b/spec/finders/ci/runners_finder_spec.rb
@@ -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' } }
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index 46073b37a3b..862fd58df04 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -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)
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 89db1ffa8dd..8253cb90ce5 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -352,6 +352,7 @@ project:
- cluster_project
- creator
- cycle_analytics_stages
+- value_streams
- group
- namespace
- management_clusters
diff --git a/spec/lib/sidebars/projects/menus/deployments_menu_spec.rb b/spec/lib/sidebars/projects/menus/deployments_menu_spec.rb
new file mode 100644
index 00000000000..4a60dfde674
--- /dev/null
+++ b/spec/lib/sidebars/projects/menus/deployments_menu_spec.rb
@@ -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
diff --git a/spec/lib/sidebars/projects/menus/operations_menu_spec.rb b/spec/lib/sidebars/projects/menus/operations_menu_spec.rb
index 5f52ae0fd7a..4168b577d5e 100644
--- a/spec/lib/sidebars/projects/menus/operations_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/operations_menu_spec.rb
@@ -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 'Logs' do
- let(:item_id) { :logs }
+ describe 'Metrics Dashboard' do
+ let(:item_id) { :metrics }
- specify { is_expected.not_to be_nil }
+ it_behaves_like 'access rights checks'
+ end
- describe 'when the user does not have access' do
- let(:user) { nil }
+ describe 'Logs' do
+ let(:item_id) { :logs }
- 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
diff --git a/spec/lib/sidebars/projects/menus/project_information_menu_spec.rb b/spec/lib/sidebars/projects/menus/project_information_menu_spec.rb
index d58766ae5e0..d081f3a8b91 100644
--- a/spec/lib/sidebars/projects/menus/project_information_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/project_information_menu_spec.rb
@@ -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
diff --git a/spec/migrations/20210503105845_add_project_value_stream_id_to_project_stages_spec.rb b/spec/migrations/20210503105845_add_project_value_stream_id_to_project_stages_spec.rb
new file mode 100644
index 00000000000..6e1cc63e42a
--- /dev/null
+++ b/spec/migrations/20210503105845_add_project_value_stream_id_to_project_stages_spec.rb
@@ -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
diff --git a/spec/models/analytics/cycle_analytics/project_stage_spec.rb b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
index fce31af619c..9efe90e7d41 100644
--- a/spec/models/analytics/cycle_analytics/project_stage_spec.rb
+++ b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
@@ -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
diff --git a/spec/models/analytics/cycle_analytics/project_value_stream_spec.rb b/spec/models/analytics/cycle_analytics/project_value_stream_spec.rb
new file mode 100644
index 00000000000..d84ecedc634
--- /dev/null
+++ b/spec/models/analytics/cycle_analytics/project_value_stream_spec.rb
@@ -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
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index e5ed8d89145..43cea0ddcd1 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -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) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0c35b1265ba..cb34917f073 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -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])
diff --git a/spec/serializers/analytics/cycle_analytics/stage_entity_spec.rb b/spec/serializers/analytics/cycle_analytics/stage_entity_spec.rb
new file mode 100644
index 00000000000..90cc7f7827b
--- /dev/null
+++ b/spec/serializers/analytics/cycle_analytics/stage_entity_spec.rb
@@ -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
diff --git a/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb b/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb
new file mode 100644
index 00000000000..24f0123ed3b
--- /dev/null
+++ b/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb
@@ -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
diff --git a/spec/services/users/ban_service_spec.rb b/spec/services/users/ban_service_spec.rb
new file mode 100644
index 00000000000..0e6ac615da5
--- /dev/null
+++ b/spec/services/users/ban_service_spec.rb
@@ -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
diff --git a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
index 17948d648cb..d23f95b2e9e 100644
--- a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
+++ b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
@@ -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
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 694ba0f1f4f..d633a3d7a9d 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -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
diff --git a/spec/workers/packages/nuget/extraction_worker_spec.rb b/spec/workers/packages/nuget/extraction_worker_spec.rb
index 3cc2c79176b..5186c037dc5 100644
--- a/spec/workers/packages/nuget/extraction_worker_spec.rb
+++ b/spec/workers/packages/nuget/extraction_worker_spec.rb
@@ -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
diff --git a/spec/workers/packages/rubygems/extraction_worker_spec.rb b/spec/workers/packages/rubygems/extraction_worker_spec.rb
index 6f65dceacf7..0e67f3ac62e 100644
--- a/spec/workers/packages/rubygems/extraction_worker_spec.rb
+++ b/spec/workers/packages/rubygems/extraction_worker_spec.rb
@@ -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 }