summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-13 12:15:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-13 12:15:42 +0000
commitb4d5fdae4298581813f0bd5fec029da91f9dfe05 (patch)
tree08b92c331a866b58793db93fbb08a07672de9610
parente91ff9d11fe29eb0a30e68b95e8c5700986575c8 (diff)
downloadgitlab-ce-b4d5fdae4298581813f0bd5fec029da91f9dfe05.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab-ci.yml3
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml2
-rw-r--r--.rubocop.yml6
-rw-r--r--.rubocop_todo/background_migration/missing_dictionary_file.yml4
-rw-r--r--.rubocop_todo/rspec/missing_feature_category.yml1
-rw-r--r--.rubocop_todo/style/if_unless_modifier.yml1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/super_sidebar/components/context_switcher_toggle.vue8
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss15
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss3
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss3
-rw-r--r--app/helpers/dropdowns_helper.rb2
-rw-r--r--app/helpers/nav_helper.rb2
-rw-r--r--app/helpers/sidebars_helper.rb7
-rw-r--r--app/models/container_registry/data_repair_detail.rb10
-rw-r--r--app/models/packages/rpm/repository_file.rb2
-rw-r--r--app/models/project.rb20
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/services/ci/catalog/add_resource_service.rb41
-rw-r--r--app/services/ci/catalog/validate_resource_service.rb46
-rw-r--r--app/services/import/github/cancel_project_import_service.rb2
-rw-r--r--app/views/dashboard/todos/index.html.haml2
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_user_profile.html.haml1
-rw-r--r--app/views/search/_results_status.html.haml3
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/form/_type_selector.html.haml2
-rw-r--r--app/views/users/show.html.haml5
-rw-r--r--config/feature_flags/development/disable_update_max_seats_worker.yml8
-rw-r--r--config/metrics/counts_28d/20230306134018_github_import_project_cancelled_monthly.yml26
-rw-r--r--config/metrics/counts_28d/20230306134609_github_import_project_partially_completed_monthly.yml26
-rw-r--r--config/metrics/counts_7d/20230306133608_github_import_project_cancelled_weekly.yml26
-rw-r--r--config/metrics/counts_7d/20230306134308_github_import_project_partially_completed_weekly.yml26
-rw-r--r--data/deprecations/15-9-rails-error-tracking.yml4
-rw-r--r--db/docs/container_registry_data_repair_details.yml10
-rw-r--r--db/migrate/20230308163018_create_container_registry_data_repair_details.rb17
-rw-r--r--db/schema_migrations/202303081630181
-rw-r--r--db/structure.sql13
-rw-r--r--doc/update/deprecations.md6
-rw-r--r--doc/update/index.md4
-rw-r--r--doc/user/group/saml_sso/index.md16
-rw-r--r--lib/generators/batched_background_migration/batched_background_migration_generator.rb5
-rw-r--r--lib/generators/batched_background_migration/templates/batched_background_migration_dictionary.template2
-rw-r--r--lib/gitlab/exception_log_formatter.rb4
-rw-r--r--lib/gitlab/i18n.rb22
-rw-r--r--lib/gitlab/import/import_failure_service.rb7
-rw-r--r--lib/gitlab/import/metrics.rb32
-rw-r--r--lib/gitlab/usage_data_counters/known_events/importer_events.yml6
-rw-r--r--lib/sidebars/user_profile/base_menu.rb12
-rw-r--r--lib/sidebars/user_profile/menus/activity_menu.rb24
-rw-r--r--lib/sidebars/user_profile/menus/contributed_projects_menu.rb24
-rw-r--r--lib/sidebars/user_profile/menus/followers_menu.rb38
-rw-r--r--lib/sidebars/user_profile/menus/following_menu.rb38
-rw-r--r--lib/sidebars/user_profile/menus/groups_menu.rb24
-rw-r--r--lib/sidebars/user_profile/menus/overview_menu.rb24
-rw-r--r--lib/sidebars/user_profile/menus/personal_projects_menu.rb24
-rw-r--r--lib/sidebars/user_profile/menus/snippets_menu.rb24
-rw-r--r--lib/sidebars/user_profile/menus/starred_projects_menu.rb24
-rw-r--r--lib/sidebars/user_profile/panel.rb52
-rw-r--r--lib/tasks/gitlab/db.rake2
-rw-r--r--locale/gitlab.pot6
-rw-r--r--package.json2
-rw-r--r--qa/qa/flow/saml.rb2
-rw-r--r--rubocop/cop/background_migration/missing_dictionary_file.rb59
-rw-r--r--spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js50
-rw-r--r--spec/helpers/sidebars_helper_spec.rb4
-rw-r--r--spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb5
-rw-r--r--spec/lib/generators/batched_background_migration/expected_files/my_batched_migration_dictionary_matcher.txt (renamed from spec/lib/generators/batched_background_migration/expected_files/my_batched_migration_dictionary.txt)4
-rw-r--r--spec/lib/gitlab/exception_log_formatter_spec.rb6
-rw-r--r--spec/lib/gitlab/import/import_failure_service_spec.rb1
-rw-r--r--spec/lib/gitlab/import/metrics_spec.rb112
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml2
-rw-r--r--spec/lib/sidebars/user_profile/menus/activity_menu_spec.rb11
-rw-r--r--spec/lib/sidebars/user_profile/menus/contributed_projects_menu_spec.rb11
-rw-r--r--spec/lib/sidebars/user_profile/menus/followers_menu_spec.rb13
-rw-r--r--spec/lib/sidebars/user_profile/menus/following_menu_spec.rb13
-rw-r--r--spec/lib/sidebars/user_profile/menus/groups_menu_spec.rb11
-rw-r--r--spec/lib/sidebars/user_profile/menus/overview_menu_spec.rb11
-rw-r--r--spec/lib/sidebars/user_profile/menus/personal_projects_menu_spec.rb11
-rw-r--r--spec/lib/sidebars/user_profile/menus/snippets_menu_spec.rb11
-rw-r--r--spec/lib/sidebars/user_profile/menus/starred_projects_menu_spec.rb11
-rw-r--r--spec/lib/sidebars/user_profile/panel_spec.rb24
-rw-r--r--spec/models/container_registry/data_repair_detail_spec.rb11
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/policies/project_policy_spec.rb20
-rw-r--r--spec/rubocop/cop/background_migration/missing_dictionary_file_spec.rb137
-rw-r--r--spec/services/ci/catalog/add_resource_service_spec.rb55
-rw-r--r--spec/services/ci/catalog/validate_resource_service_spec.rb57
-rw-r--r--spec/services/import/github/cancel_project_import_service_spec.rb2
-rw-r--r--spec/support/rspec_order_todo.yml1
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb10
-rw-r--r--spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb89
-rw-r--r--yarn.lock33
93 files changed, 1421 insertions, 144 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4925f9ec268..79f99211bb1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -131,7 +131,7 @@ workflow:
variables:
PG_VERSION: "12"
- DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}.patched-golang-${GO_VERSION}-rust-${RUST_VERSION}-node-16.14-postgresql-${PG_VERSION}:rubygems-3.2-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36"
+ DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}.patched-golang-${GO_VERSION}-rust-${RUST_VERSION}-node-16.14-postgresql-${PG_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36"
# We set $GITLAB_DEPENDENCY_PROXY to another variable (since it's set at the group level and has higher precedence than .gitlab-ci.yml)
# so that we can override $GITLAB_DEPENDENCY_PROXY_ADDRESS in workflow rules.
GITLAB_DEPENDENCY_PROXY_ADDRESS: "${GITLAB_DEPENDENCY_PROXY}"
@@ -151,6 +151,7 @@ variables:
CHROME_VERSION: "109"
DOCKER_VERSION: "23.0.1"
RUBY_VERSION: "2.7"
+ RUBYGEMS_VERSION: "3.4"
GO_VERSION: "1.18"
RUST_VERSION: "1.65"
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 6bf014a6324..59758d2aec7 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -3,7 +3,7 @@
- .default-retry
- .default-before_script
- .assets-compile-cache
- image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-node-16.14:rubygems-3.2-git-2.33-lfs-2.9-yarn-1.22-graphicsmagick-1.3.36
+ image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-node-16.14:rubygems-${RUBYGEMS_VERSION}-git-2.33-lfs-2.9-yarn-1.22-graphicsmagick-1.3.36
variables:
SETUP_DB: "false"
WEBPACK_VENDOR_DLL: "true"
diff --git a/.rubocop.yml b/.rubocop.yml
index 8f9a02bbadd..2de8a88633b 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -440,6 +440,12 @@ BackgroundMigration/FeatureCategory:
Include:
- 'lib/gitlab/background_migration/*.rb'
+BackgroundMigration/MissingDictionaryFile:
+ Enabled: true
+ EnforcedSince: 20230307160251
+ Include:
+ - 'db/post_migrate/*.rb'
+
# See https://gitlab.com/gitlab-org/gitlab/-/issues/373194
Gitlab/RSpec/AvoidSetup:
Enabled: true
diff --git a/.rubocop_todo/background_migration/missing_dictionary_file.yml b/.rubocop_todo/background_migration/missing_dictionary_file.yml
new file mode 100644
index 00000000000..c065a1ac3aa
--- /dev/null
+++ b/.rubocop_todo/background_migration/missing_dictionary_file.yml
@@ -0,0 +1,4 @@
+---
+# Grace period will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/395354
+BackgroundMigration/MissingDictionaryFile:
+ Details: grace period
diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml
index faf8f9f7d4c..8a679bb9e96 100644
--- a/.rubocop_todo/rspec/missing_feature_category.yml
+++ b/.rubocop_todo/rspec/missing_feature_category.yml
@@ -1135,7 +1135,6 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/lib/omni_auth/strategies/kerberos_spec.rb'
- 'ee/spec/lib/peek/views/elasticsearch_spec.rb'
- 'ee/spec/lib/quality/seeders/vulnerabilities_spec.rb'
- - 'ee/spec/lib/sidebars/groups/menus/administration_menu_spec.rb'
- 'ee/spec/lib/sidebars/groups/menus/analytics_menu_spec.rb'
- 'ee/spec/lib/sidebars/groups/menus/security_compliance_menu_spec.rb'
- 'ee/spec/lib/slack/api_spec.rb'
diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml
index cda00940294..19016646725 100644
--- a/.rubocop_todo/style/if_unless_modifier.yml
+++ b/.rubocop_todo/style/if_unless_modifier.yml
@@ -642,7 +642,6 @@ Style/IfUnlessModifier:
- 'ee/lib/gitlab/usage/metrics/instrumentations/count_users_creating_ci_builds_metric.rb'
- 'ee/lib/gitlab/usage/metrics/instrumentations/license_metric.rb'
- 'ee/lib/omni_auth/strategies/group_saml.rb'
- - 'ee/lib/sidebars/groups/menus/administration_menu.rb'
- 'ee/lib/sidebars/groups/menus/analytics_menu.rb'
- 'ee/lib/sidebars/groups/menus/security_compliance_menu.rb'
- 'ee/lib/tasks/geo.rake'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 8bf2fa93b54..5cdfcb682a1 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-60b05a6155531cdfd5e51b2ed3c6e888f105048b
+d1ce57834e67383564e6d7faf7827be3a7dd8afe
diff --git a/app/assets/javascripts/super_sidebar/components/context_switcher_toggle.vue b/app/assets/javascripts/super_sidebar/components/context_switcher_toggle.vue
index 7a2cc47d3a7..e0b6870872c 100644
--- a/app/assets/javascripts/super_sidebar/components/context_switcher_toggle.vue
+++ b/app/assets/javascripts/super_sidebar/components/context_switcher_toggle.vue
@@ -11,6 +11,9 @@ export default {
CollapseToggle: GlCollapseToggleDirective,
},
props: {
+ /*
+ * Contains metadata about the current view, e.g. `id`, `title` and `avatar`
+ */
context: {
type: Object,
required: true,
@@ -24,6 +27,9 @@ export default {
collapseIcon() {
return this.expanded ? 'chevron-up' : 'chevron-down';
},
+ avatarShape() {
+ return this.context.avatar_shape || 'rect';
+ },
},
};
</script>
@@ -43,7 +49,7 @@ export default {
<gl-avatar
v-else
:size="24"
- shape="rect"
+ :shape="avatarShape"
:entity-name="context.title"
:entity-id="context.id"
:src="context.avatar"
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 279830c4569..67fc73729a3 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -143,11 +143,24 @@
.dropdown-menu-toggle.dropdown-menu-toggle {
justify-content: flex-start;
overflow: hidden;
+ padding-top: #{$gl-padding-8 - 1};
+ padding-bottom: #{$gl-padding-8 - 1};
padding-right: 25px;
position: relative;
text-overflow: ellipsis;
+ line-height: $gl-line-height;
width: 160px;
+ &:hover {
+ @include gl-inset-border-1-gray-400;
+ }
+
+ &:hover,
+ &:focus {
+ background-color: $gray-50;
+ border-color: $gray-400;
+ }
+
.gl-spinner {
position: absolute;
top: 9px;
@@ -157,7 +170,7 @@
.dropdown-menu-toggle-icon {
position: absolute;
right: $gl-padding-8;
- color: $gray-darkest;
+ color: $gray-500;
}
}
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index cc7fe8243d2..d4270235b0d 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -661,9 +661,12 @@ html {
.dropdown-menu-toggle.dropdown-menu-toggle {
justify-content: flex-start;
overflow: hidden;
+ padding-top: 7px;
+ padding-bottom: 7px;
padding-right: 25px;
position: relative;
text-overflow: ellipsis;
+ line-height: 16px;
width: 160px;
}
.dropdown-menu {
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index 9fa1f6ed402..005b356aae7 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -661,9 +661,12 @@ html {
.dropdown-menu-toggle.dropdown-menu-toggle {
justify-content: flex-start;
overflow: hidden;
+ padding-top: 7px;
+ padding-bottom: 7px;
padding-right: 25px;
position: relative;
text-overflow: ellipsis;
+ line-height: 16px;
width: 160px;
}
.dropdown-menu {
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 427cbe18fbf..475ba3dcba8 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -54,7 +54,7 @@ module DropdownsHelper
default_label = data_attr[:default_label]
content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
- output << sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
+ output << sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon")
output.html_safe
end
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 563a58d6eb3..7f0a928c62c 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -97,7 +97,7 @@ module NavHelper
def super_sidebar_supported?
return true if @nav.nil?
- %w(your_work project group profile).include?(@nav)
+ %w(your_work project group profile user_profile).include?(@nav)
end
def get_header_links
diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb
index 9ea30b055c8..756040986db 100644
--- a/app/helpers/sidebars_helper.rb
+++ b/app/helpers/sidebars_helper.rb
@@ -90,7 +90,9 @@ module SidebarsHelper
}
end
- def super_sidebar_nav_panel(nav: nil, project: nil, user: nil, group: nil, current_ref: nil, ref_type: nil)
+ def super_sidebar_nav_panel(
+ nav: nil, project: nil, user: nil, group: nil, current_ref: nil, ref_type: nil,
+ viewed_user: nil)
context_adds = { route_is_active: method(:active_nav_link?), is_super_sidebar: true }
case nav
when 'project'
@@ -102,6 +104,9 @@ module SidebarsHelper
when 'profile'
context = Sidebars::Context.new(current_user: user, container: user, **context_adds)
Sidebars::UserSettings::Panel.new(context)
+ when 'user_profile'
+ context = Sidebars::Context.new(current_user: user, container: viewed_user, **context_adds)
+ Sidebars::UserProfile::Panel.new(context)
else
context = your_work_sidebar_context(user, **context_adds)
Sidebars::YourWork::Panel.new(context)
diff --git a/app/models/container_registry/data_repair_detail.rb b/app/models/container_registry/data_repair_detail.rb
new file mode 100644
index 00000000000..09e617e69f5
--- /dev/null
+++ b/app/models/container_registry/data_repair_detail.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module ContainerRegistry
+ class DataRepairDetail < ApplicationRecord
+ self.table_name = 'container_registry_data_repair_details'
+ self.primary_key = :project_id
+
+ belongs_to :project, optional: false
+ end
+end
diff --git a/app/models/packages/rpm/repository_file.rb b/app/models/packages/rpm/repository_file.rb
index 614ec9b3e56..bbd435691d2 100644
--- a/app/models/packages/rpm/repository_file.rb
+++ b/app/models/packages/rpm/repository_file.rb
@@ -13,7 +13,7 @@ module Packages
enum status: { default: 0, pending_destruction: 1, processing: 2, error: 3 }
- belongs_to :project, inverse_of: :repository_files
+ belongs_to :project, inverse_of: :rpm_repository_files
validates :project, presence: true
validates :file, presence: true
diff --git a/app/models/project.rb b/app/models/project.rb
index f07090d6131..af640bd8296 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -243,14 +243,22 @@ class Project < ApplicationRecord
has_many :fork_network_projects, through: :fork_network, source: :projects
# Packages
- has_many :packages, class_name: 'Packages::Package'
- has_many :package_files, through: :packages, class_name: 'Packages::PackageFile'
+ has_many :packages,
+ class_name: 'Packages::Package'
+ has_many :package_files,
+ through: :packages, class_name: 'Packages::PackageFile'
# repository_files must be destroyed by ruby code in order to properly remove carrierwave uploads
- has_many :repository_files, inverse_of: :project, class_name: 'Packages::Rpm::RepositoryFile',
- dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :rpm_repository_files,
+ inverse_of: :project,
+ class_name: 'Packages::Rpm::RepositoryFile',
+ dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# debian_distributions and associated component_files must be destroyed by ruby code in order to properly remove carrierwave uploads
- has_many :debian_distributions, class_name: 'Packages::Debian::ProjectDistribution', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- has_one :packages_cleanup_policy, class_name: 'Packages::Cleanup::Policy', inverse_of: :project
+ has_many :debian_distributions,
+ class_name: 'Packages::Debian::ProjectDistribution',
+ dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_one :packages_cleanup_policy,
+ class_name: 'Packages::Cleanup::Policy',
+ inverse_of: :project
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index e18a48d2d5f..8b6286c5a4a 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -261,6 +261,7 @@ class ProjectPolicy < BasePolicy
enable :reporter_access
enable :developer_access
enable :maintainer_access
+ enable :add_catalog_resource
enable :change_namespace
enable :change_visibility_level
diff --git a/app/services/ci/catalog/add_resource_service.rb b/app/services/ci/catalog/add_resource_service.rb
new file mode 100644
index 00000000000..1f53513b7d1
--- /dev/null
+++ b/app/services/ci/catalog/add_resource_service.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Ci
+ module Catalog
+ class AddResourceService
+ include Gitlab::Allowable
+
+ attr_reader :project, :current_user
+
+ def initialize(project, user)
+ @current_user = user
+ @project = project
+ end
+
+ def execute
+ raise Gitlab::Access::AccessDeniedError unless can?(current_user, :add_catalog_resource, project)
+
+ validation_response = Ci::Catalog::ValidateResourceService.new(project, project.default_branch).execute
+
+ if validation_response.success?
+ create_catalog_resource
+ else
+ ServiceResponse.error(message: validation_response.message)
+ end
+ end
+
+ private
+
+ def create_catalog_resource
+ catalog_resource = Ci::Catalog::Resource.new(project: project)
+
+ if catalog_resource.valid?
+ catalog_resource.save!
+ ServiceResponse.success(payload: catalog_resource)
+ else
+ ServiceResponse.error(message: catalog_resource.errors.full_messages.join(', '))
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/ci/catalog/validate_resource_service.rb b/app/services/ci/catalog/validate_resource_service.rb
new file mode 100644
index 00000000000..f166c220869
--- /dev/null
+++ b/app/services/ci/catalog/validate_resource_service.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Ci
+ module Catalog
+ class ValidateResourceService
+ attr_reader :project
+
+ def initialize(project, ref)
+ @project = project
+ @ref = ref
+ @errors = []
+ end
+
+ def execute
+ check_project_readme
+ check_project_description
+
+ if errors.empty?
+ ServiceResponse.success
+ else
+ ServiceResponse.error(message: errors.join(' , '))
+ end
+ end
+
+ private
+
+ attr_reader :ref, :errors
+
+ def check_project_description
+ return if project.description.present?
+
+ errors << 'Project must have a description'
+ end
+
+ def check_project_readme
+ return if project_has_readme?
+
+ errors << 'Project must have a README'
+ end
+
+ def project_has_readme?
+ project.repository.blob_data_at(ref, 'README.md')
+ end
+ end
+ end
+end
diff --git a/app/services/import/github/cancel_project_import_service.rb b/app/services/import/github/cancel_project_import_service.rb
index 616ae54451f..62cd0c95eaf 100644
--- a/app/services/import/github/cancel_project_import_service.rb
+++ b/app/services/import/github/cancel_project_import_service.rb
@@ -9,7 +9,7 @@ module Import
if project.import_in_progress?
project.import_state.cancel
- metrics.track_import_state
+ metrics.track_canceled_import
success(project: project)
else
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 9e59f9d700f..d182cb9f391 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -62,7 +62,7 @@
= sort_options_hash[@sort]
- else
= sort_title_recently_created
- = sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon gl-top-3')
+ = sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon')
%ul.dropdown-menu.dropdown-menu-sort.dropdown-menu-right
%li
= link_to todos_filter_path(sort: sort_value_label_priority) do
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index d03e24d2457..ec80c2ba353 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -5,7 +5,7 @@
-# Render the parent group sidebar while creating a new subgroup/project, see GroupsController#new.
- group = @parent_group || @group
- - sidebar_panel = super_sidebar_nav_panel(nav: nav, user: current_user, group: group, project: @project, current_ref: current_ref, ref_type: @ref_type)
+ - sidebar_panel = super_sidebar_nav_panel(nav: nav, user: current_user, group: group, project: @project, current_ref: current_ref, ref_type: @ref_type, viewed_user: @user)
- sidebar_data = super_sidebar_context(current_user, group: group, project: @project, panel: sidebar_panel).to_json
%aside.js-super-sidebar.super-sidebar.super-sidebar-loading{ data: { root_path: root_path, sidebar: sidebar_data, toggle_new_nav_endpoint: profile_preferences_url } }
diff --git a/app/views/layouts/nav/sidebar/_user_profile.html.haml b/app/views/layouts/nav/sidebar/_user_profile.html.haml
new file mode 100644
index 00000000000..b24334f48c4
--- /dev/null
+++ b/app/views/layouts/nav/sidebar/_user_profile.html.haml
@@ -0,0 +1 @@
+= render partial: 'shared/nav/sidebar', object: Sidebars::UserProfile::Panel.new(Sidebars::Context.new(current_user: current_user, container: @user))
diff --git a/app/views/search/_results_status.html.haml b/app/views/search/_results_status.html.haml
index 2321abb31df..4ab68caaf22 100644
--- a/app/views/search/_results_status.html.haml
+++ b/app/views/search/_results_status.html.haml
@@ -5,7 +5,8 @@
.gl-p-5.gl-display-flex
.gl-md-display-flex.gl-text-left.gl-align-items-center.gl-flex-grow-1.gl-white-space-nowrap.gl-max-w-full
- unless @search_service_presenter.without_count?
- = search_entries_info(@search_objects, @scope, @search_term)
+ .gl-text-truncate
+ = search_entries_info(@search_objects, @scope, @search_term)
- unless @search_service_presenter.show_snippets?
- if @project
- link_to_project = link_to(@project.full_name, @project, class: 'ml-md-1 gl-text-truncate search-wrap-f-md-down')
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index af63839d7c1..3c4ee01d04f 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -26,7 +26,7 @@
- apply_is_default_styles = (selected.nil? || selected.empty?) && !no_default_styles
%span.dropdown-toggle-text{ class: ("is-default" if apply_is_default_styles) }
= multi_label_name(selected, label_name)
- = sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
+ = sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon")
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height
= render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create }
- if show_create && project && can?(current_user, :admin_label, project)
diff --git a/app/views/shared/issuable/form/_type_selector.html.haml b/app/views/shared/issuable/form/_type_selector.html.haml
index a94ef70b2d5..6d4cd83d55b 100644
--- a/app/views/shared/issuable/form/_type_selector.html.haml
+++ b/app/views/shared/issuable/form/_type_selector.html.haml
@@ -10,7 +10,7 @@
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.dropdown-toggle-text.is-default
= issuable.issue_type.capitalize || _("Select type")
- = sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
+ = sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon")
.dropdown-menu.dropdown-menu-selectable.dropdown-select
.dropdown-title.gl-display-flex
%span.gl-ml-auto
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 7ba3aa8f12e..c27086ded6f 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -6,6 +6,9 @@
- page_itemtype 'http://schema.org/Person'
- add_page_specific_style 'page_bundles/profile'
- link_classes = "flex-grow-1 mx-1 "
+- if show_super_sidebar?
+ - @left_sidebar = true
+ - nav "user_profile"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
@@ -120,7 +123,7 @@
= @user.bio
- if !profile_tabs.empty? && !Feature.enabled?(:profile_tabs_vue, current_user)
- .scrolling-tabs-container
+ .scrolling-tabs-container{ class: [('gl-display-none' if show_super_sidebar?)] }
.fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs
diff --git a/config/feature_flags/development/disable_update_max_seats_worker.yml b/config/feature_flags/development/disable_update_max_seats_worker.yml
new file mode 100644
index 00000000000..ae0fe482ee9
--- /dev/null
+++ b/config/feature_flags/development/disable_update_max_seats_worker.yml
@@ -0,0 +1,8 @@
+---
+name: disable_update_max_seats_worker
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114127
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/382725
+milestone: '15.10'
+type: development
+group: group::utilization
+default_enabled: false
diff --git a/config/metrics/counts_28d/20230306134018_github_import_project_cancelled_monthly.yml b/config/metrics/counts_28d/20230306134018_github_import_project_cancelled_monthly.yml
new file mode 100644
index 00000000000..f398fa3c16f
--- /dev/null
+++ b/config/metrics/counts_28d/20230306134018_github_import_project_cancelled_monthly.yml
@@ -0,0 +1,26 @@
+---
+key_path: redis_hll_counters.importer.github_import_project_cancelled_monthly
+description: The number of github projects that were cancelled monthly
+product_section: dev
+product_stage: manage
+product_group: import
+product_category: importers
+value_type: number
+status: active
+milestone: "15.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113724
+time_frame: 28d
+data_source: redis_hll
+data_category: optional
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - github_import_project_cancelled
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_28d/20230306134609_github_import_project_partially_completed_monthly.yml b/config/metrics/counts_28d/20230306134609_github_import_project_partially_completed_monthly.yml
new file mode 100644
index 00000000000..10344cffe5f
--- /dev/null
+++ b/config/metrics/counts_28d/20230306134609_github_import_project_partially_completed_monthly.yml
@@ -0,0 +1,26 @@
+---
+key_path: redis_hll_counters.importer.github_import_project_partially_completed_monthly
+description: The number of github projects that were partially completed monthly
+product_section: dev
+product_stage: manage
+product_group: import
+product_category: importers
+value_type: number
+status: active
+milestone: "15.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113724
+time_frame: 28d
+data_source: redis_hll
+data_category: optional
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - github_import_project_partially_completed
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_7d/20230306133608_github_import_project_cancelled_weekly.yml b/config/metrics/counts_7d/20230306133608_github_import_project_cancelled_weekly.yml
new file mode 100644
index 00000000000..2d76d88e2e7
--- /dev/null
+++ b/config/metrics/counts_7d/20230306133608_github_import_project_cancelled_weekly.yml
@@ -0,0 +1,26 @@
+---
+key_path: redis_hll_counters.importer.github_import_project_cancelled_weekly
+description: The number of github projects that were cancelled weekly
+product_section: dev
+product_stage: manage
+product_group: import
+product_category: importers
+value_type: number
+status: active
+milestone: "15.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113724
+time_frame: 7d
+data_source: redis_hll
+data_category: optional
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - github_import_project_cancelled
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_7d/20230306134308_github_import_project_partially_completed_weekly.yml b/config/metrics/counts_7d/20230306134308_github_import_project_partially_completed_weekly.yml
new file mode 100644
index 00000000000..9bc42eafd23
--- /dev/null
+++ b/config/metrics/counts_7d/20230306134308_github_import_project_partially_completed_weekly.yml
@@ -0,0 +1,26 @@
+---
+key_path: redis_hll_counters.importer.github_import_project_partially_completed_weekly
+description: The number of github projects that were partially completed weekly
+product_section: dev
+product_stage: manage
+product_group: import
+product_category: importers
+value_type: number
+status: active
+milestone: "15.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113724
+time_frame: 7d
+data_source: redis_hll
+data_category: optional
+instrumentation_class: RedisHLLMetric
+options:
+ events:
+ - github_import_project_partially_completed
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/data/deprecations/15-9-rails-error-tracking.yml b/data/deprecations/15-9-rails-error-tracking.yml
index 42ec004afa9..4d9fc371213 100644
--- a/data/deprecations/15-9-rails-error-tracking.yml
+++ b/data/deprecations/15-9-rails-error-tracking.yml
@@ -1,12 +1,12 @@
- title: "Error Tracking UI in GitLab Rails is deprecated"
announcement_milestone: "15.9"
- removal_milestone: "16.0"
+ removal_milestone: "16.6"
breaking_change: true
reporter: kbychu
stage: monitor
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/389991
body: |
- The [Error Tracking UI](https://docs.gitlab.com/ee/operations/error_tracking.html) is deprecated in 15.9 and will be removed in 16.0. In future versions, you should use the [GitLab Observability UI](https://gitlab.com/gitlab-org/opstrace/opstrace-ui/), which will gradually be made available on GitLab.com over the next few releases.
+ The [Error Tracking UI](https://docs.gitlab.com/ee/operations/error_tracking.html) is deprecated in 15.9 and will be removed in 16.6 (milestone might change) once GitLab Observability UI is made available. In future versions, you should use the [GitLab Observability UI](https://gitlab.com/gitlab-org/opstrace/opstrace-ui/), which will gradually be made available on GitLab.com over the next few releases.
During the transition to the GitLab Observability UI, we will migrate the [GitLab Observability Backend](https://gitlab.com/gitlab-org/opstrace/opstrace) from a per-cluster deployment model to a per-tenant deployment model. Because [Integrated Error Tracking](https://docs.gitlab.com/ee/operations/error_tracking.html#integrated-error-tracking) is in Open Beta, we will not migrate any existing user data. For more details about the migration, see the direction pages for:
diff --git a/db/docs/container_registry_data_repair_details.yml b/db/docs/container_registry_data_repair_details.yml
new file mode 100644
index 00000000000..c258fff4832
--- /dev/null
+++ b/db/docs/container_registry_data_repair_details.yml
@@ -0,0 +1,10 @@
+---
+table_name: container_registry_data_repair_details
+classes:
+- ContainerRegistry::DataRepairDetail
+feature_categories:
+- container_registry
+description: Contains details for the container registry data repair
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113029
+milestone: '15.10'
+gitlab_schema: gitlab_main
diff --git a/db/migrate/20230308163018_create_container_registry_data_repair_details.rb b/db/migrate/20230308163018_create_container_registry_data_repair_details.rb
new file mode 100644
index 00000000000..b682eb6ee7e
--- /dev/null
+++ b/db/migrate/20230308163018_create_container_registry_data_repair_details.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CreateContainerRegistryDataRepairDetails < Gitlab::Database::Migration[2.1]
+ enable_lock_retries!
+
+ def change
+ create_table :container_registry_data_repair_details, id: false do |t|
+ t.integer :missing_count, default: 0
+ t.references :project,
+ primary_key: true,
+ default: nil,
+ index: false,
+ foreign_key: { to_table: :projects, on_delete: :cascade }
+ t.timestamps_with_timezone null: false
+ end
+ end
+end
diff --git a/db/schema_migrations/20230308163018 b/db/schema_migrations/20230308163018
new file mode 100644
index 00000000000..19815455ef3
--- /dev/null
+++ b/db/schema_migrations/20230308163018
@@ -0,0 +1 @@
+f9132e8d1d39307fc4f9ef17c6e044bab636d17ae7a7e5207f26ab3e38441638 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 5fc3ebcb69c..8686191f19d 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -14523,6 +14523,13 @@ CREATE TABLE container_expiration_policies (
CONSTRAINT container_expiration_policies_name_regex_keep CHECK ((char_length(name_regex_keep) <= 255))
);
+CREATE TABLE container_registry_data_repair_details (
+ missing_count integer DEFAULT 0,
+ project_id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL
+);
+
CREATE TABLE container_repositories (
id integer NOT NULL,
project_id integer NOT NULL,
@@ -26505,6 +26512,9 @@ ALTER TABLE ONLY compliance_management_frameworks
ALTER TABLE ONLY container_expiration_policies
ADD CONSTRAINT container_expiration_policies_pkey PRIMARY KEY (project_id);
+ALTER TABLE ONLY container_registry_data_repair_details
+ ADD CONSTRAINT container_registry_data_repair_details_pkey PRIMARY KEY (project_id);
+
ALTER TABLE ONLY container_repositories
ADD CONSTRAINT container_repositories_pkey PRIMARY KEY (id);
@@ -36229,6 +36239,9 @@ ALTER TABLE ONLY packages_debian_project_component_files
ALTER TABLE ONLY namespace_aggregation_schedules
ADD CONSTRAINT fk_rails_b565c8d16c FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+ALTER TABLE ONLY container_registry_data_repair_details
+ ADD CONSTRAINT fk_rails_b70d8111d9 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
ALTER TABLE batched_background_migration_job_transition_logs
ADD CONSTRAINT fk_rails_b7523a175b FOREIGN KEY (batched_background_migration_job_id) REFERENCES batched_background_migration_jobs(id) ON DELETE CASCADE;
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index c57892fd43d..4bef8e29df7 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -210,17 +210,17 @@ Users on self-managed instances should update their pipelines to ensure they do
</div>
-<div class="deprecation removal-160 breaking-change">
+<div class="deprecation removal-166 breaking-change">
### Error Tracking UI in GitLab Rails is deprecated
-Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span>
+Planned removal: GitLab <span class="removal-milestone">16.6</span> <span class="removal-date"></span>
WARNING:
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
Review the details carefully before upgrading.
-The [Error Tracking UI](https://docs.gitlab.com/ee/operations/error_tracking.html) is deprecated in 15.9 and will be removed in 16.0. In future versions, you should use the [GitLab Observability UI](https://gitlab.com/gitlab-org/opstrace/opstrace-ui/), which will gradually be made available on GitLab.com over the next few releases.
+The [Error Tracking UI](https://docs.gitlab.com/ee/operations/error_tracking.html) is deprecated in 15.9 and will be removed in 16.6 (milestone might change) once GitLab Observability UI is made available. In future versions, you should use the [GitLab Observability UI](https://gitlab.com/gitlab-org/opstrace/opstrace-ui/), which will gradually be made available on GitLab.com over the next few releases.
During the transition to the GitLab Observability UI, we will migrate the [GitLab Observability Backend](https://gitlab.com/gitlab-org/opstrace/opstrace) from a per-cluster deployment model to a per-tenant deployment model. Because [Integrated Error Tracking](https://docs.gitlab.com/ee/operations/error_tracking.html#integrated-error-tracking) is in Open Beta, we will not migrate any existing user data. For more details about the migration, see the direction pages for:
diff --git a/doc/update/index.md b/doc/update/index.md
index 2117ba07055..e2e46086e9d 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -269,7 +269,7 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
- **Upgrade to patch release 15.9.3 or later**. This provides fixes for two database migration bugs:
- Patch releases 15.9.0, 15.9.1, 15.9.2 have [a bug that can cause data loss](#user-profile-data-loss-bug-in-159x) from the user profile fields.
- The second [bug fix](https://gitlab.com/gitlab-org/gitlab/-/issues/394760) ensures it is possible to upgrade directly from 15.4.x.
-- As part of the [CI Partitioning effort](../architecture/blueprints/ci_data_decay/pipeline_partitioning.md), a [new Foreign Key](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107547) was added to `ci_builds_needs`. On GitLab instances with large CI tables, adding this constraint can take longer than usual. Make sure that this migration is finished before upgrading to 15.9.
+- As part of the [CI Partitioning effort](../architecture/blueprints/ci_data_decay/pipeline_partitioning.md), a [new Foreign Key](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107547) was added to `ci_builds_needs`. On GitLab instances with large CI tables, adding this constraint can take longer than usual.
- Praefect's metadata verifier's [invalid metadata deletion behavior](../administration/gitaly/praefect.md#enable-deletions) is now enabled by default.
The metadata verifier processes replica records in the Praefect database and verifies the replicas actually exist on the Gitaly nodes. If the replica doesn't exist, its
@@ -1461,7 +1461,7 @@ If your organization uses these fields either wait for the bug fix to be release
Organizations that are already running earlier patch levels of GitLab 15.6, 15.7, or 15.8 can proceed with steps 2 and 3.
-If you have already upgraded to GitLab 15.9 following these instructions, your instance will not be affected by this bug, and you don't need to apply the 15.9.x patch when it is released.
+If you have already upgraded to GitLab 15.9 following these instructions, your instance will not be affected by this bug, and you don't need to apply the 15.9.x patch when it is released.
See [issue 393216](https://gitlab.com/gitlab-org/gitlab/-/issues/393216) for more information.
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 2d0e642b3ef..8e574c4fbd1 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -325,10 +325,7 @@ After you have configured your identity provider, you can:
To change the identity provider:
-- If the `NameID` is not identical in the existing and new identity providers,
- tell users to:
- 1. [Unlink the current SAML identity](#unlinking-accounts).
- 1. [Link their identity](#user-access-and-management) to the new identity provider.
+- If the `NameID` is not identical in the existing and new identity providers, [change the NameID for users](#change-nameid-for-one-or-more-users).
- If the `NameID` is identical, users do not have to make any changes.
### Migrate to a different identity provider
@@ -340,19 +337,17 @@ users cannot access any of the SAML groups. To mitigate this, you can disable
To migrate identity providers:
1. [Configure](#configure-your-identity-provider) the group with the new identity provider.
-1. Tell users to:
- 1. [Unlink their account from the group](#unlinking-accounts).
- 1. [Link their account to the new SAML app](#linking-saml-to-your-existing-gitlabcom-account).
+1. [Change the NameID for users](#change-nameid-for-one-or-more-users).
### Change email domains
To migrate users to a new email domain, tell users to:
1. Add their new email as the primary email to their accounts and verify it.
-1. [Unlink their account from the group](#unlinking-accounts).
-1. [Link their account to the group](#linking-saml-to-your-existing-gitlabcom-account).
1. Optional. Remove their old email from the account.
+If the NameID is configured with the email address, [change the NameID for users](#change-nameid-for-one-or-more-users).
+
## User access and management
> - SAML user provisioning [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/268142) in GitLab 13.7.
@@ -395,7 +390,8 @@ On subsequent visits, you should be able to go [sign in to GitLab.com with SAML]
> Update of SAML identities using the SAML API [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227841) in GitLab 15.5.
-Group owners can update the SAML identities for their group members using the [SAML API](../../../api/saml.md).
+Group owners can update the SAML identities for their group members using the [SAML API](../../../api/saml.md#update-extern_uid-field-for-a-saml-identity).
+If [SCIM](scim_setup.md) is configured, group owners can update the SCIM identities using the [SCIM API](../../../api/scim.md#update-extern_uid-field-for-a-scim-identity).
Alternatively, ask the users to reconnect their SAML account.
diff --git a/lib/generators/batched_background_migration/batched_background_migration_generator.rb b/lib/generators/batched_background_migration/batched_background_migration_generator.rb
index 96c2b160744..c68ed52c1a0 100644
--- a/lib/generators/batched_background_migration/batched_background_migration_generator.rb
+++ b/lib/generators/batched_background_migration/batched_background_migration_generator.rb
@@ -64,5 +64,10 @@ module BatchedBackgroundMigration
def feature_category
options[:feature_category]
end
+
+ def current_milestone
+ version = Gem::Version.new(File.read('VERSION'))
+ version.release.segments.first(2).join('.')
+ end
end
end
diff --git a/lib/generators/batched_background_migration/templates/batched_background_migration_dictionary.template b/lib/generators/batched_background_migration/templates/batched_background_migration_dictionary.template
index 7cc7091191d..8aa08e15f48 100644
--- a/lib/generators/batched_background_migration/templates/batched_background_migration_dictionary.template
+++ b/lib/generators/batched_background_migration/templates/batched_background_migration_dictionary.template
@@ -3,4 +3,4 @@ migration_job_name: <%= class_name %>
description: # Please capture what <%= class_name %> does
feature_category: <%= feature_category %>
introduced_by_url: # URL of the MR (or issue/commit) that introduced the migration
-milestone:
+milestone: <%= current_milestone %>
diff --git a/lib/gitlab/exception_log_formatter.rb b/lib/gitlab/exception_log_formatter.rb
index ce802b562f0..52ad67d6f8b 100644
--- a/lib/gitlab/exception_log_formatter.rb
+++ b/lib/gitlab/exception_log_formatter.rb
@@ -17,6 +17,10 @@ module Gitlab
payload['exception.backtrace'] = Rails.backtrace_cleaner.clean(exception.backtrace)
end
+ if exception.cause
+ payload['exception.cause_class'] = exception.cause.class.name
+ end
+
if sql = find_sql(exception)
payload['exception.sql'] = sql
end
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index 971737e21af..a1b6e937396 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -44,28 +44,28 @@ module Gitlab
TRANSLATION_LEVELS = {
'bg' => 0,
'cs_CZ' => 0,
- 'da_DK' => 34,
- 'de' => 16,
+ 'da_DK' => 33,
+ 'de' => 15,
'en' => 100,
'eo' => 0,
- 'es' => 33,
+ 'es' => 32,
'fil_PH' => 0,
'fr' => 99,
'gl_ES' => 0,
'id_ID' => 0,
'it' => 1,
'ja' => 31,
- 'ko' => 20,
- 'nb_NO' => 23,
+ 'ko' => 19,
+ 'nb_NO' => 22,
'nl_NL' => 0,
'pl_PL' => 3,
- 'pt_BR' => 57,
- 'ro_RO' => 91,
- 'ru' => 26,
- 'si_LK' => 11,
+ 'pt_BR' => 56,
+ 'ro_RO' => 89,
+ 'ru' => 25,
+ 'si_LK' => 10,
'tr_TR' => 10,
- 'uk' => 55,
- 'zh_CN' => 98,
+ 'uk' => 53,
+ 'zh_CN' => 96,
'zh_HK' => 1,
'zh_TW' => 98
}.freeze
diff --git a/lib/gitlab/import/import_failure_service.rb b/lib/gitlab/import/import_failure_service.rb
index 8257f9f5db4..714d9b3edd9 100644
--- a/lib/gitlab/import/import_failure_service.rb
+++ b/lib/gitlab/import/import_failure_service.rb
@@ -89,13 +89,8 @@ module Gitlab
)
end
- def import_metrics
- @import_metrics ||= Gitlab::Import::Metrics.new("#{project.import_type}_importer", project)
- end
-
def track_metrics
- import_metrics.track_failed_import
- import_metrics.track_import_state
+ Gitlab::Import::Metrics.new("#{project.import_type}_importer", project).track_failed_import
end
end
end
diff --git a/lib/gitlab/import/metrics.rb b/lib/gitlab/import/metrics.rb
index 82f8cf8dbc6..e457d9ec57c 100644
--- a/lib/gitlab/import/metrics.rb
+++ b/lib/gitlab/import/metrics.rb
@@ -26,25 +26,20 @@ module Gitlab
observe_histogram
projects_counter.increment
track_finish_metric
- track_import_state
end
def track_failed_import
return unless project.github_import?
track_usage_event(:github_import_project_failure, project.id)
+ track_import_state('github')
end
- def track_import_state
+ def track_canceled_import
return unless project.github_import?
- Gitlab::Tracking.event(
- importer,
- 'create',
- label: 'github_import_project_state',
- project: project,
- extra: { import_type: 'github', state: project.beautified_import_status_name }
- )
+ track_usage_event(:github_import_project_cancelled, project.id)
+ track_import_state('github')
end
def issues_counter
@@ -88,7 +83,24 @@ module Gitlab
def track_finish_metric
return unless project.github_import?
- track_usage_event(:github_import_project_success, project.id)
+ track_import_state('github')
+
+ case project.beautified_import_status_name
+ when 'partially completed'
+ track_usage_event(:github_import_project_partially_completed, project.id)
+ when 'completed'
+ track_usage_event(:github_import_project_success, project.id)
+ end
+ end
+
+ def track_import_state(type)
+ Gitlab::Tracking.event(
+ importer,
+ 'create',
+ label: "#{type}_import_project_state",
+ project: project,
+ extra: { import_type: type, state: project.beautified_import_status_name }
+ )
end
end
end
diff --git a/lib/gitlab/usage_data_counters/known_events/importer_events.yml b/lib/gitlab/usage_data_counters/known_events/importer_events.yml
index ece8cd6aa45..3050fff5d92 100644
--- a/lib/gitlab/usage_data_counters/known_events/importer_events.yml
+++ b/lib/gitlab/usage_data_counters/known_events/importer_events.yml
@@ -9,3 +9,9 @@
- name: github_import_project_failure
redis_slot: import
aggregation: weekly
+- name: github_import_project_cancelled
+ redis_slot: import
+ aggregation: weekly
+- name: github_import_project_partially_completed
+ redis_slot: import
+ aggregation: weekly
diff --git a/lib/sidebars/user_profile/base_menu.rb b/lib/sidebars/user_profile/base_menu.rb
new file mode 100644
index 00000000000..5594d7c3c65
--- /dev/null
+++ b/lib/sidebars/user_profile/base_menu.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module UserProfile
+ class BaseMenu < ::Sidebars::Menu
+ override :render?
+ def render?
+ can?(context.current_user, :read_user_profile, context.container)
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/user_profile/menus/activity_menu.rb b/lib/sidebars/user_profile/menus/activity_menu.rb
new file mode 100644
index 00000000000..95e99a3dcbc
--- /dev/null
+++ b/lib/sidebars/user_profile/menus/activity_menu.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module UserProfile
+ module Menus
+ class ActivityMenu < ::Sidebars::UserProfile::BaseMenu
+ override :link
+ def link
+ user_activity_path(context.container)
+ end
+
+ override :title
+ def title
+ s_('UserProfile|Activity')
+ end
+
+ override :active_routes
+ def active_routes
+ { path: 'users#activity' }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/user_profile/menus/contributed_projects_menu.rb b/lib/sidebars/user_profile/menus/contributed_projects_menu.rb
new file mode 100644
index 00000000000..0644982a143
--- /dev/null
+++ b/lib/sidebars/user_profile/menus/contributed_projects_menu.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module UserProfile
+ module Menus
+ class ContributedProjectsMenu < ::Sidebars::UserProfile::BaseMenu
+ override :link
+ def link
+ user_contributed_projects_path(context.container)
+ end
+
+ override :title
+ def title
+ s_('UserProfile|Contributed projects')
+ end
+
+ override :active_routes
+ def active_routes
+ { path: 'users#contributed' }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/user_profile/menus/followers_menu.rb b/lib/sidebars/user_profile/menus/followers_menu.rb
new file mode 100644
index 00000000000..53411c21a86
--- /dev/null
+++ b/lib/sidebars/user_profile/menus/followers_menu.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module UserProfile
+ module Menus
+ class FollowersMenu < ::Sidebars::UserProfile::BaseMenu
+ include Gitlab::Utils::StrongMemoize
+
+ override :link
+ def link
+ user_followers_path(context.container)
+ end
+
+ override :title
+ def title
+ s_('UserProfile|Followers')
+ end
+
+ override :active_routes
+ def active_routes
+ { path: 'users#followers' }
+ end
+
+ override :has_pill?
+ def has_pill?
+ context.container.followers.any?
+ end
+ strong_memoize_attr :has_pill?
+
+ override :pill_count
+ def pill_count
+ context.container.followers.count
+ end
+ strong_memoize_attr :pill_count
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/user_profile/menus/following_menu.rb b/lib/sidebars/user_profile/menus/following_menu.rb
new file mode 100644
index 00000000000..5da76a0100a
--- /dev/null
+++ b/lib/sidebars/user_profile/menus/following_menu.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module UserProfile
+ module Menus
+ class FollowingMenu < ::Sidebars::UserProfile::BaseMenu
+ include Gitlab::Utils::StrongMemoize
+
+ override :link
+ def link
+ user_following_path(context.container)
+ end
+
+ override :title
+ def title
+ s_('UserProfile|Following')
+ end
+
+ override :active_routes
+ def active_routes
+ { path: 'users#following' }
+ end
+
+ override :has_pill?
+ def has_pill?
+ context.container.followees.any?
+ end
+ strong_memoize_attr :has_pill?
+
+ override :pill_count
+ def pill_count
+ context.container.followees.count
+ end
+ strong_memoize_attr :pill_count
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/user_profile/menus/groups_menu.rb b/lib/sidebars/user_profile/menus/groups_menu.rb
new file mode 100644
index 00000000000..38c2c3f042d
--- /dev/null
+++ b/lib/sidebars/user_profile/menus/groups_menu.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module UserProfile
+ module Menus
+ class GroupsMenu < ::Sidebars::UserProfile::BaseMenu
+ override :link
+ def link
+ user_groups_path(context.container)
+ end
+
+ override :title
+ def title
+ s_('UserProfile|Groups')
+ end
+
+ override :active_routes
+ def active_routes
+ { path: 'users#groups' }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/user_profile/menus/overview_menu.rb b/lib/sidebars/user_profile/menus/overview_menu.rb
new file mode 100644
index 00000000000..ebf184e7e4e
--- /dev/null
+++ b/lib/sidebars/user_profile/menus/overview_menu.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module UserProfile
+ module Menus
+ class OverviewMenu < ::Sidebars::UserProfile::BaseMenu
+ override :link
+ def link
+ user_path(context.container)
+ end
+
+ override :title
+ def title
+ s_('UserProfile|Overview')
+ end
+
+ override :active_routes
+ def active_routes
+ { path: 'users#show' }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/user_profile/menus/personal_projects_menu.rb b/lib/sidebars/user_profile/menus/personal_projects_menu.rb
new file mode 100644
index 00000000000..14fcddf2cc2
--- /dev/null
+++ b/lib/sidebars/user_profile/menus/personal_projects_menu.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module UserProfile
+ module Menus
+ class PersonalProjectsMenu < ::Sidebars::UserProfile::BaseMenu
+ override :link
+ def link
+ user_projects_path(context.container)
+ end
+
+ override :title
+ def title
+ s_('UserProfile|Personal projects')
+ end
+
+ override :active_routes
+ def active_routes
+ { path: 'users#projects' }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/user_profile/menus/snippets_menu.rb b/lib/sidebars/user_profile/menus/snippets_menu.rb
new file mode 100644
index 00000000000..a8e5077098c
--- /dev/null
+++ b/lib/sidebars/user_profile/menus/snippets_menu.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module UserProfile
+ module Menus
+ class SnippetsMenu < ::Sidebars::UserProfile::BaseMenu
+ override :link
+ def link
+ user_snippets_path(context.container)
+ end
+
+ override :title
+ def title
+ s_('UserProfile|Snippets')
+ end
+
+ override :active_routes
+ def active_routes
+ { path: 'users#snippets' }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/user_profile/menus/starred_projects_menu.rb b/lib/sidebars/user_profile/menus/starred_projects_menu.rb
new file mode 100644
index 00000000000..762b451f4a1
--- /dev/null
+++ b/lib/sidebars/user_profile/menus/starred_projects_menu.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module UserProfile
+ module Menus
+ class StarredProjectsMenu < ::Sidebars::UserProfile::BaseMenu
+ override :link
+ def link
+ user_starred_projects_path(context.container)
+ end
+
+ override :title
+ def title
+ s_('UserProfile|Starred projects')
+ end
+
+ override :active_routes
+ def active_routes
+ { path: 'users#starred' }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/user_profile/panel.rb b/lib/sidebars/user_profile/panel.rb
new file mode 100644
index 00000000000..9a595fdf64c
--- /dev/null
+++ b/lib/sidebars/user_profile/panel.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module UserProfile
+ class Panel < ::Sidebars::Panel
+ include UsersHelper
+
+ override :configure_menus
+ def configure_menus
+ add_menus
+ end
+
+ override :aria_label
+ def aria_label
+ s_('UserProfile|User profile navigation')
+ end
+
+ override :super_sidebar_context_header
+ def super_sidebar_context_header
+ @super_sidebar_context_header ||= {
+ title: user_name,
+ avatar: context.container.avatar_url,
+ avatar_shape: 'circle'
+ }
+ end
+
+ private
+
+ def user
+ context.container
+ end
+
+ def user_name
+ return user_display_name(user) if user.blocked? || !user.confirmed?
+
+ user.name
+ end
+
+ def add_menus
+ add_menu(Sidebars::UserProfile::Menus::OverviewMenu.new(context))
+ add_menu(Sidebars::UserProfile::Menus::ActivityMenu.new(context))
+ add_menu(Sidebars::UserProfile::Menus::GroupsMenu.new(context))
+ add_menu(Sidebars::UserProfile::Menus::ContributedProjectsMenu.new(context))
+ add_menu(Sidebars::UserProfile::Menus::PersonalProjectsMenu.new(context))
+ add_menu(Sidebars::UserProfile::Menus::StarredProjectsMenu.new(context))
+ add_menu(Sidebars::UserProfile::Menus::SnippetsMenu.new(context))
+ add_menu(Sidebars::UserProfile::Menus::FollowersMenu.new(context))
+ add_menu(Sidebars::UserProfile::Menus::FollowingMenu.new(context))
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index befc2113b1e..72fe190bc8f 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -458,7 +458,7 @@ namespace :gitlab do
Rails.application.eager_load!
version = Gem::Version.new(File.read('VERSION'))
- milestone = version.release.segments[0..1].join('.')
+ milestone = version.release.segments.first(2).join('.')
classes = {}
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 21316a79726..62debd10678 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3627,9 +3627,6 @@ msgstr ""
msgid "AdminUsers|user cap"
msgstr ""
-msgid "Administration"
-msgstr ""
-
msgid "Administrators"
msgstr ""
@@ -47224,6 +47221,9 @@ msgstr ""
msgid "UserProfile|User ID: %{id}"
msgstr ""
+msgid "UserProfile|User profile navigation"
+msgstr ""
+
msgid "UserProfile|View all"
msgstr ""
diff --git a/package.json b/package.json
index 03552da4056..4364eb97805 100644
--- a/package.json
+++ b/package.json
@@ -225,7 +225,7 @@
"cheerio": "^1.0.0-rc.9",
"commander": "^2.20.3",
"custom-jquery-matchers": "^2.1.0",
- "eslint": "8.34.0",
+ "eslint": "8.35.0",
"eslint-import-resolver-jest": "3.0.2",
"eslint-import-resolver-webpack": "0.13.2",
"eslint-plugin-import": "^2.27.5",
diff --git a/qa/qa/flow/saml.rb b/qa/qa/flow/saml.rb
index 8a0ebb9f551..1cf29e75c67 100644
--- a/qa/qa/flow/saml.rb
+++ b/qa/qa/flow/saml.rb
@@ -19,8 +19,6 @@ module QA
end
def enable_saml_sso(group, saml_idp_service, enforce_sso: false, default_membership_role: 'Guest')
- Runtime::Feature.enable(:group_administration_nav_item)
-
page.visit Runtime::Scenario.gitlab_address
Page::Main::Login.perform(&:sign_in_using_credentials) unless Page::Main::Menu.perform(&:signed_in?)
diff --git a/rubocop/cop/background_migration/missing_dictionary_file.rb b/rubocop/cop/background_migration/missing_dictionary_file.rb
new file mode 100644
index 00000000000..9158b268bf9
--- /dev/null
+++ b/rubocop/cop/background_migration/missing_dictionary_file.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module BackgroundMigration
+ # Checks the batched background migration has the corresponding dictionary file
+ class MissingDictionaryFile < RuboCop::Cop::Base
+ include MigrationHelpers
+
+ MSG = "Missing %{file_name}. " \
+ "Use the generator 'batched_background_migration' to create dictionary files automatically. " \
+ "For more details refer: https://docs.gitlab.com/ee/development/database/batched_background_migrations.html#generator"
+
+ DICTIONARY_DIR = "db/docs/batched_background_migrations"
+
+ def_node_matcher :batched_background_migration_name_node, <<~PATTERN
+ `(send nil? :queue_batched_background_migration $_ ...)
+ PATTERN
+
+ def_node_matcher :migration_constant_value, <<~PATTERN
+ `(casgn nil? %const_name ({sym|str} $_))
+ PATTERN
+
+ def on_class(node)
+ return unless time_enforced?(node) && in_post_deployment_migration?(node)
+
+ migration_name_node = batched_background_migration_name_node(node)
+ return unless migration_name_node
+
+ migration_name = if migration_name_node.const_name.present?
+ migration_constant_value(node, const_name: migration_name_node.const_name.to_sym)
+ else
+ migration_name_node.value
+ end
+
+ return if dictionary_file?(migration_name)
+
+ add_offense(node, message: format(MSG, file_name: dictionary_file_path(migration_name)))
+ end
+
+ private
+
+ def dictionary_file?(migration_class_name)
+ File.exist?(dictionary_file_path(migration_class_name))
+ end
+
+ def dictionary_file_path(migration_class_name)
+ File.join(rails_root, DICTIONARY_DIR, "#{migration_class_name.underscore}.yml")
+ end
+
+ def rails_root
+ @rails_root ||= File.expand_path('../../..', __dir__)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js b/spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js
new file mode 100644
index 00000000000..7172b60d0fa
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js
@@ -0,0 +1,50 @@
+import { GlAvatar } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ContextSwitcherToggle from '~/super_sidebar/components/context_switcher_toggle.vue';
+
+describe('ContextSwitcherToggle component', () => {
+ let wrapper;
+
+ const context = {
+ id: 1,
+ title: 'Title',
+ avatar: '/path/to/avatar.png',
+ };
+
+ const findGlAvatar = () => wrapper.getComponent(GlAvatar);
+
+ const createWrapper = (props = {}) => {
+ wrapper = shallowMountExtended(ContextSwitcherToggle, {
+ propsData: {
+ context,
+ expanded: false,
+ ...props,
+ },
+ });
+ };
+
+ describe('with an avatar', () => {
+ it('passes the correct props to GlAvatar', () => {
+ createWrapper();
+ const avatar = findGlAvatar();
+
+ expect(avatar.props('shape')).toBe('rect');
+ expect(avatar.props('entityName')).toBe(context.title);
+ expect(avatar.props('entityId')).toBe(context.id);
+ expect(avatar.props('src')).toBe(context.avatar);
+ });
+
+ it('renders the avatar with a custom shape', () => {
+ const customShape = 'circle';
+ createWrapper({
+ context: {
+ ...context,
+ avatar_shape: customShape,
+ },
+ });
+ const avatar = findGlAvatar();
+
+ expect(avatar.props('shape')).toBe(customShape);
+ });
+ });
+});
diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb
index f23de35877b..b602fe3548e 100644
--- a/spec/helpers/sidebars_helper_spec.rb
+++ b/spec/helpers/sidebars_helper_spec.rb
@@ -218,6 +218,10 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
expect(helper.super_sidebar_nav_panel(nav: 'profile')).to be_a(Sidebars::UserSettings::Panel)
end
+ it 'returns User profile Panel for user profile nav' do
+ expect(helper.super_sidebar_nav_panel(nav: 'user_profile')).to be_a(Sidebars::UserProfile::Panel)
+ end
+
it 'returns "Your Work" Panel for your_work nav', :use_clean_rails_memory_store_caching do
expect(helper.super_sidebar_nav_panel(nav: 'your_work', user: user)).to be_a(Sidebars::YourWork::Panel)
end
diff --git a/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb b/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb
index 017548b6fb5..d533bcf0039 100644
--- a/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb
+++ b/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe BatchedBackgroundMigration::BatchedBackgroundMigrationGenerator,
let(:expected_migration_spec_file) { load_expected_file('queue_my_batched_migration_spec.txt') }
let(:expected_migration_job_file) { load_expected_file('my_batched_migration.txt') }
let(:expected_migration_job_spec_file) { load_expected_file('my_batched_migration_spec_matcher.txt') }
- let(:expected_migration_dictionary) { load_expected_file('my_batched_migration_dictionary.txt') }
+ let(:expected_migration_dictionary) { load_expected_file('my_batched_migration_dictionary_matcher.txt') }
it 'generates expected files' do
run_generator %w[my_batched_migration --table_name=projects --column_name=id --feature_category=database]
@@ -48,7 +48,8 @@ RSpec.describe BatchedBackgroundMigration::BatchedBackgroundMigrationGenerator,
end
assert_file('db/docs/batched_background_migrations/my_batched_migration.yml') do |migration_dictionary|
- expect(migration_dictionary).to eq(expected_migration_dictionary)
+ # Regex is used to match the dynamically generated 'milestone' in the dictionary
+ expect(migration_dictionary).to match(/#{expected_migration_dictionary}/)
end
end
end
diff --git a/spec/lib/generators/batched_background_migration/expected_files/my_batched_migration_dictionary.txt b/spec/lib/generators/batched_background_migration/expected_files/my_batched_migration_dictionary_matcher.txt
index 0b350961abd..6280d35177e 100644
--- a/spec/lib/generators/batched_background_migration/expected_files/my_batched_migration_dictionary.txt
+++ b/spec/lib/generators/batched_background_migration/expected_files/my_batched_migration_dictionary_matcher.txt
@@ -2,5 +2,5 @@
migration_job_name: MyBatchedMigration
description: # Please capture what MyBatchedMigration does
feature_category: database
-introduced_by_url: # URL of the MR (or issue/commit) that introduced the migration
-milestone:
+introduced_by_url: # URL of the MR \(or issue/commit\) that introduced the migration
+milestone: [0-9\.]+
diff --git a/spec/lib/gitlab/exception_log_formatter_spec.rb b/spec/lib/gitlab/exception_log_formatter_spec.rb
index 7dda56f0bf5..82166971603 100644
--- a/spec/lib/gitlab/exception_log_formatter_spec.rb
+++ b/spec/lib/gitlab/exception_log_formatter_spec.rb
@@ -45,6 +45,12 @@ RSpec.describe Gitlab::ExceptionLogFormatter do
allow(exception).to receive(:cause).and_return(ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1'))
end
+ it 'adds the cause_class to payload' do
+ described_class.format!(exception, payload)
+
+ expect(payload['exception.cause_class']).to eq('ActiveRecord::StatementInvalid')
+ end
+
it 'adds the normalized SQL query to payload' do
described_class.format!(exception, payload)
diff --git a/spec/lib/gitlab/import/import_failure_service_spec.rb b/spec/lib/gitlab/import/import_failure_service_spec.rb
index fc02c6bd4ca..eb71b307b8d 100644
--- a/spec/lib/gitlab/import/import_failure_service_spec.rb
+++ b/spec/lib/gitlab/import/import_failure_service_spec.rb
@@ -141,7 +141,6 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures do
.with("#{project.import_type}_importer", project)
.and_return(metrics_double)
expect(metrics_double).to receive(:track_failed_import)
- expect(metrics_double).to receive(:track_import_state)
service.execute
end
diff --git a/spec/lib/gitlab/import/metrics_spec.rb b/spec/lib/gitlab/import/metrics_spec.rb
index f0140a975b1..1a988af0dbd 100644
--- a/spec/lib/gitlab/import/metrics_spec.rb
+++ b/spec/lib/gitlab/import/metrics_spec.rb
@@ -11,7 +11,6 @@ RSpec.describe Gitlab::Import::Metrics, :aggregate_failures do
subject { described_class.new(importer, project) }
before do
- allow(Gitlab::Metrics).to receive(:counter) { counter }
allow(counter).to receive(:increment)
allow(histogram).to receive(:observe)
end
@@ -42,6 +41,13 @@ RSpec.describe Gitlab::Import::Metrics, :aggregate_failures do
context 'when project is not a github import' do
it 'does not emit importer metrics' do
expect(subject).not_to receive(:track_usage_event)
+ expect_no_snowplow_event(
+ category: :test_importer,
+ action: 'create',
+ label: 'github_import_project_state',
+ project: project,
+ extra: { import_type: 'github', state: 'failed' }
+ )
subject.track_failed_import
end
@@ -50,22 +56,15 @@ RSpec.describe Gitlab::Import::Metrics, :aggregate_failures do
context 'when project is a github import' do
before do
project.import_type = 'github'
+ allow(project).to receive(:import_status).and_return('failed')
end
it 'emits importer metrics' do
expect(subject).to receive(:track_usage_event).with(:github_import_project_failure, project.id)
subject.track_failed_import
- end
- end
- end
-
- describe '#track_import_state' do
- context 'when project is not a github import' do
- it 'does not emit importer metrics' do
- subject.track_import_state
- expect_no_snowplow_event(
+ expect_snowplow_event(
category: :test_importer,
action: 'create',
label: 'github_import_project_state',
@@ -74,50 +73,64 @@ RSpec.describe Gitlab::Import::Metrics, :aggregate_failures do
)
end
end
+ end
+ describe '#track_finished_import' do
context 'when project is a github import' do
before do
project.import_type = 'github'
- allow(project).to receive(:import_status).and_return('failed')
+ allow(Gitlab::Metrics).to receive(:counter) { counter }
+ allow(Gitlab::Metrics).to receive(:histogram) { histogram }
+ allow(project).to receive(:beautified_import_status_name).and_return('completed')
end
it 'emits importer metrics' do
- subject.track_import_state
+ expect(Gitlab::Metrics).to receive(:counter).with(
+ :test_importer_imported_projects_total,
+ 'The number of imported projects'
+ )
+
+ expect(Gitlab::Metrics).to receive(:histogram).with(
+ :test_importer_total_duration_seconds,
+ 'Total time spent importing projects, in seconds',
+ {},
+ described_class::IMPORT_DURATION_BUCKETS
+ )
+
+ expect(counter).to receive(:increment)
+
+ subject.track_finished_import
expect_snowplow_event(
category: :test_importer,
action: 'create',
label: 'github_import_project_state',
project: project,
- extra: { import_type: 'github', state: 'failed' }
+ extra: { import_type: 'github', state: 'completed' }
)
- end
- end
- end
- describe '#track_finished_import' do
- before do
- allow(Gitlab::Metrics).to receive(:histogram) { histogram }
- end
-
- it 'emits importer metrics' do
- expect(Gitlab::Metrics).to receive(:counter).with(
- :test_importer_imported_projects_total,
- 'The number of imported projects'
- )
+ expect(subject.duration).not_to be_nil
+ end
- expect(Gitlab::Metrics).to receive(:histogram).with(
- :test_importer_total_duration_seconds,
- 'Total time spent importing projects, in seconds',
- {},
- described_class::IMPORT_DURATION_BUCKETS
- )
+ context 'when import is partially completed' do
+ before do
+ allow(project).to receive(:beautified_import_status_name).and_return('partially completed')
+ end
- expect(counter).to receive(:increment)
+ it 'emits snowplow metrics' do
+ expect(subject).to receive(:track_usage_event).with(:github_import_project_partially_completed, project.id)
- subject.track_finished_import
+ subject.track_finished_import
- expect(subject.duration).not_to be_nil
+ expect_snowplow_event(
+ category: :test_importer,
+ action: 'create',
+ label: 'github_import_project_state',
+ project: project,
+ extra: { import_type: 'github', state: 'partially completed' }
+ )
+ end
+ end
end
context 'when project is not a github import' do
@@ -126,7 +139,6 @@ RSpec.describe Gitlab::Import::Metrics, :aggregate_failures do
subject.track_finished_import
- expect(histogram).to have_received(:observe).with({ importer: :test_importer }, anything)
expect_no_snowplow_event(
category: :test_importer,
action: 'create',
@@ -136,23 +148,41 @@ RSpec.describe Gitlab::Import::Metrics, :aggregate_failures do
)
end
end
+ end
+
+ describe '#track_cancelled_import' do
+ context 'when project is not a github import' do
+ it 'does not emit importer metrics' do
+ expect(subject).not_to receive(:track_usage_event)
+ expect_no_snowplow_event(
+ category: :test_importer,
+ action: 'create',
+ label: 'github_import_project_state',
+ project: project,
+ extra: { import_type: 'github', state: 'canceled' }
+ )
+
+ subject.track_canceled_import
+ end
+ end
context 'when project is a github import' do
before do
project.import_type = 'github'
- allow(project).to receive(:import_status).and_return('finished')
- allow(project).to receive(:import_finished?).and_return(true)
+ allow(project).to receive(:import_status).and_return('canceled')
end
- it 'emits snowplow metrics' do
- subject.track_finished_import
+ it 'emits importer metrics' do
+ expect(subject).to receive(:track_usage_event).with(:github_import_project_cancelled, project.id)
+
+ subject.track_canceled_import
expect_snowplow_event(
category: :test_importer,
action: 'create',
label: 'github_import_project_state',
project: project,
- extra: { import_type: 'github', state: 'completed' }
+ extra: { import_type: 'github', state: 'canceled' }
)
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index ebd2db63fe5..ca784ead25a 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -704,7 +704,7 @@ project:
- project_registry
- packages
- package_files
-- repository_files
+- rpm_repository_files
- packages_cleanup_policy
- alerting_setting
- project_setting
diff --git a/spec/lib/sidebars/user_profile/menus/activity_menu_spec.rb b/spec/lib/sidebars/user_profile/menus/activity_menu_spec.rb
new file mode 100644
index 00000000000..44492380f11
--- /dev/null
+++ b/spec/lib/sidebars/user_profile/menus/activity_menu_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::UserProfile::Menus::ActivityMenu, feature_category: :navigation do
+ it_behaves_like 'User profile menu',
+ title: s_('UserProfile|Activity'),
+ active_route: 'users#activity' do
+ let(:link) { "/users/#{user.username}/activity" }
+ end
+end
diff --git a/spec/lib/sidebars/user_profile/menus/contributed_projects_menu_spec.rb b/spec/lib/sidebars/user_profile/menus/contributed_projects_menu_spec.rb
new file mode 100644
index 00000000000..a5371c36fd3
--- /dev/null
+++ b/spec/lib/sidebars/user_profile/menus/contributed_projects_menu_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::UserProfile::Menus::ContributedProjectsMenu, feature_category: :navigation do
+ it_behaves_like 'User profile menu',
+ title: s_('UserProfile|Contributed projects'),
+ active_route: 'users#contributed' do
+ let(:link) { "/users/#{user.username}/contributed" }
+ end
+end
diff --git a/spec/lib/sidebars/user_profile/menus/followers_menu_spec.rb b/spec/lib/sidebars/user_profile/menus/followers_menu_spec.rb
new file mode 100644
index 00000000000..1b3efbf4ceb
--- /dev/null
+++ b/spec/lib/sidebars/user_profile/menus/followers_menu_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::UserProfile::Menus::FollowersMenu, feature_category: :navigation do
+ it_behaves_like 'User profile menu',
+ title: s_('UserProfile|Followers'),
+ active_route: 'users#followers' do
+ let(:link) { "/users/#{user.username}/followers" }
+ end
+
+ it_behaves_like 'Followers/followees counts', :followers
+end
diff --git a/spec/lib/sidebars/user_profile/menus/following_menu_spec.rb b/spec/lib/sidebars/user_profile/menus/following_menu_spec.rb
new file mode 100644
index 00000000000..167961f085e
--- /dev/null
+++ b/spec/lib/sidebars/user_profile/menus/following_menu_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::UserProfile::Menus::FollowingMenu, feature_category: :navigation do
+ it_behaves_like 'User profile menu',
+ title: s_('UserProfile|Following'),
+ active_route: 'users#following' do
+ let(:link) { "/users/#{user.username}/following" }
+ end
+
+ it_behaves_like 'Followers/followees counts', :followees
+end
diff --git a/spec/lib/sidebars/user_profile/menus/groups_menu_spec.rb b/spec/lib/sidebars/user_profile/menus/groups_menu_spec.rb
new file mode 100644
index 00000000000..6c48ad2e8d0
--- /dev/null
+++ b/spec/lib/sidebars/user_profile/menus/groups_menu_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::UserProfile::Menus::GroupsMenu, feature_category: :navigation do
+ it_behaves_like 'User profile menu',
+ title: s_('UserProfile|Groups'),
+ active_route: 'users#groups' do
+ let(:link) { "/users/#{user.username}/groups" }
+ end
+end
diff --git a/spec/lib/sidebars/user_profile/menus/overview_menu_spec.rb b/spec/lib/sidebars/user_profile/menus/overview_menu_spec.rb
new file mode 100644
index 00000000000..e34f59cddf0
--- /dev/null
+++ b/spec/lib/sidebars/user_profile/menus/overview_menu_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::UserProfile::Menus::OverviewMenu, feature_category: :navigation do
+ it_behaves_like 'User profile menu',
+ title: s_('UserProfile|Overview'),
+ active_route: 'users#show' do
+ let(:link) { "/#{user.username}" }
+ end
+end
diff --git a/spec/lib/sidebars/user_profile/menus/personal_projects_menu_spec.rb b/spec/lib/sidebars/user_profile/menus/personal_projects_menu_spec.rb
new file mode 100644
index 00000000000..ba2c3d11b88
--- /dev/null
+++ b/spec/lib/sidebars/user_profile/menus/personal_projects_menu_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::UserProfile::Menus::PersonalProjectsMenu, feature_category: :navigation do
+ it_behaves_like 'User profile menu',
+ title: s_('UserProfile|Personal projects'),
+ active_route: 'users#projects' do
+ let(:link) { "/users/#{user.username}/projects" }
+ end
+end
diff --git a/spec/lib/sidebars/user_profile/menus/snippets_menu_spec.rb b/spec/lib/sidebars/user_profile/menus/snippets_menu_spec.rb
new file mode 100644
index 00000000000..2760d172a51
--- /dev/null
+++ b/spec/lib/sidebars/user_profile/menus/snippets_menu_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::UserProfile::Menus::SnippetsMenu, feature_category: :navigation do
+ it_behaves_like 'User profile menu',
+ title: s_('UserProfile|Snippets'),
+ active_route: 'users#snippets' do
+ let(:link) { "/users/#{user.username}/snippets" }
+ end
+end
diff --git a/spec/lib/sidebars/user_profile/menus/starred_projects_menu_spec.rb b/spec/lib/sidebars/user_profile/menus/starred_projects_menu_spec.rb
new file mode 100644
index 00000000000..e205d1f5492
--- /dev/null
+++ b/spec/lib/sidebars/user_profile/menus/starred_projects_menu_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::UserProfile::Menus::StarredProjectsMenu, feature_category: :navigation do
+ it_behaves_like 'User profile menu',
+ title: s_('UserProfile|Starred projects'),
+ active_route: 'users#starred' do
+ let(:link) { "/users/#{user.username}/starred" }
+ end
+end
diff --git a/spec/lib/sidebars/user_profile/panel_spec.rb b/spec/lib/sidebars/user_profile/panel_spec.rb
new file mode 100644
index 00000000000..af261dce3f3
--- /dev/null
+++ b/spec/lib/sidebars/user_profile/panel_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::UserProfile::Panel, feature_category: :navigation do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:user) { create(:user) }
+
+ let(:context) { Sidebars::Context.new(current_user: current_user, container: user) }
+
+ subject { described_class.new(context) }
+
+ it 'implements #aria_label' do
+ expect(subject.aria_label).to eq(s_('UserProfile|User profile navigation'))
+ end
+
+ it 'implements #super_sidebar_context_header' do
+ expect(subject.super_sidebar_context_header).to eq({
+ title: user.name,
+ avatar: user.avatar_url,
+ avatar_shape: 'circle'
+ })
+ end
+end
diff --git a/spec/models/container_registry/data_repair_detail_spec.rb b/spec/models/container_registry/data_repair_detail_spec.rb
new file mode 100644
index 00000000000..92833553a1e
--- /dev/null
+++ b/spec/models/container_registry/data_repair_detail_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ContainerRegistry::DataRepairDetail, type: :model, feature_category: :container_registry do
+ let_it_be(:project) { create(:project) }
+
+ subject { described_class.new(project: project) }
+
+ it { is_expected.to belong_to(:project).required }
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index e7390c99b64..92b23045b08 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -137,6 +137,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
it { is_expected.to have_many(:reviews).inverse_of(:project) }
it { is_expected.to have_many(:packages).class_name('Packages::Package') }
it { is_expected.to have_many(:package_files).class_name('Packages::PackageFile') }
+ it { is_expected.to have_many(:rpm_repository_files).class_name('Packages::Rpm::RepositoryFile').inverse_of(:project).dependent(:destroy) }
it { is_expected.to have_many(:debian_distributions).class_name('Packages::Debian::ProjectDistribution').dependent(:destroy) }
it { is_expected.to have_one(:packages_cleanup_policy).class_name('Packages::Cleanup::Policy').inverse_of(:project) }
it { is_expected.to have_many(:pipeline_artifacts).dependent(:restrict_with_error) }
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 7b2b0122768..778ddfe9d71 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -3042,6 +3042,26 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
end
end
+ describe 'add_catalog_resource' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:current_user) { public_send(role) }
+
+ where(:role, :allowed) do
+ :owner | true
+ :maintainer | false
+ :developer | false
+ :reporter | false
+ :guest | false
+ end
+
+ with_them do
+ it do
+ expect(subject.can?(:add_catalog_resource)).to be(allowed)
+ end
+ end
+ end
+
describe 'read_code' do
let(:current_user) { create(:user) }
diff --git a/spec/rubocop/cop/background_migration/missing_dictionary_file_spec.rb b/spec/rubocop/cop/background_migration/missing_dictionary_file_spec.rb
new file mode 100644
index 00000000000..32b958426b9
--- /dev/null
+++ b/spec/rubocop/cop/background_migration/missing_dictionary_file_spec.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+require 'rubocop_spec_helper'
+require_relative '../../../../rubocop/cop/background_migration/missing_dictionary_file'
+
+RSpec.describe RuboCop::Cop::BackgroundMigration::MissingDictionaryFile, feature_category: :database do
+ let(:config) do
+ RuboCop::Config.new(
+ 'BackgroundMigration/MissingDictionaryFile' => {
+ 'EnforcedSince' => 20230307160251
+ }
+ )
+ end
+
+ context 'for non post migrations' do
+ before do
+ allow(cop).to receive(:in_post_deployment_migration?).and_return(false)
+ end
+
+ it 'does not throw any offense' do
+ expect_no_offenses(<<~RUBY)
+ class QueueMyMigration < Gitlab::Database::Migration[2.1]
+ MIGRATION = 'MyMigration'
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :users,
+ :id
+ )
+ end
+ end
+ RUBY
+ end
+ end
+
+ context 'for post migrations' do
+ before do
+ allow(cop).to receive(:in_post_deployment_migration?).and_return(true)
+ end
+
+ context 'without enqueuing batched migrations' do
+ it 'does not throw any offense' do
+ expect_no_offenses(<<~RUBY)
+ class CreateTestTable < Gitlab::Database::Migration[2.1]
+ def change
+ create_table(:tests)
+ end
+ end
+ RUBY
+ end
+ end
+
+ context 'with enqueuing batched migration' do
+ let(:rails_root) { File.expand_path('../../../..', __dir__) }
+ let(:dictionary_file_path) { File.join(rails_root, 'db/docs/batched_background_migrations/my_migration.yml') }
+
+ context 'for migrations before enforced time' do
+ before do
+ allow(cop).to receive(:version).and_return(20230307160250)
+ end
+
+ it 'does not throw any offenses' do
+ expect_no_offenses(<<~RUBY)
+ class QueueMyMigration < Gitlab::Database::Migration[2.1]
+ MIGRATION = 'MyMigration'
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :users,
+ :id
+ )
+ end
+ end
+ RUBY
+ end
+ end
+
+ context 'for migrations after enforced time' do
+ before do
+ allow(cop).to receive(:version).and_return(20230307160252)
+ end
+
+ it 'throws offense on not having the appropriate dictionary file with migration name as a constant' do
+ expect_offense(<<~RUBY)
+ class QueueMyMigration < Gitlab::Database::Migration[2.1]
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{format("Missing %{file_name}. Use the generator 'batched_background_migration' to create dictionary files automatically. For more details refer: https://docs.gitlab.com/ee/development/database/batched_background_migrations.html#generator", file_name: dictionary_file_path)}
+ MIGRATION = 'MyMigration'
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :users,
+ :id
+ )
+ end
+ end
+ RUBY
+ end
+
+ it 'throws offense on not having the appropriate dictionary file with migration name as a variable' do
+ expect_offense(<<~RUBY)
+ class QueueMyMigration < Gitlab::Database::Migration[2.1]
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{format("Missing %{file_name}. Use the generator 'batched_background_migration' to create dictionary files automatically. For more details refer: https://docs.gitlab.com/ee/development/database/batched_background_migrations.html#generator", file_name: dictionary_file_path)}
+ def up
+ queue_batched_background_migration(
+ 'MyMigration',
+ :users,
+ :id
+ )
+ end
+ end
+ RUBY
+ end
+
+ it 'does not throw offense with appropriate dictionary file' do
+ expect(File).to receive(:exist?).with(dictionary_file_path).and_return(true)
+
+ expect_no_offenses(<<~RUBY)
+ class QueueMyMigration < Gitlab::Database::Migration[2.1]
+ MIGRATION = 'MyMigration'
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :users,
+ :id
+ )
+ end
+ end
+ RUBY
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/catalog/add_resource_service_spec.rb b/spec/services/ci/catalog/add_resource_service_spec.rb
new file mode 100644
index 00000000000..ecb939e3c2d
--- /dev/null
+++ b/spec/services/ci/catalog/add_resource_service_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::Catalog::AddResourceService, feature_category: :pipeline_composition do
+ let_it_be(:project) { create(:project, :repository, description: 'Our components') }
+ let_it_be(:user) { create(:user) }
+
+ let(:service) { described_class.new(project, user) }
+
+ describe '#execute' do
+ context 'with an unauthorized user' do
+ it 'raises an AccessDeniedError' do
+ expect { service.execute }.to raise_error(Gitlab::Access::AccessDeniedError)
+ end
+ end
+
+ context 'with an authorized user' do
+ before do
+ project.add_owner(user)
+ end
+
+ context 'and a valid project' do
+ it 'creates a catalog resource' do
+ response = service.execute
+
+ expect(response.payload.project).to eq(project)
+ end
+ end
+
+ context 'with an invalid project' do
+ let_it_be(:project) { create(:project, :repository) }
+
+ it 'does not create a catalog resource' do
+ response = service.execute
+
+ expect(response.message).to eq('Project must have a description')
+ end
+ end
+
+ context 'with an invalid catalog resource' do
+ it 'does not save the catalog resource' do
+ catalog_resource = instance_double(::Ci::Catalog::Resource,
+ valid?: false,
+ errors: instance_double(ActiveModel::Errors, full_messages: ['not valid']))
+ allow(::Ci::Catalog::Resource).to receive(:new).and_return(catalog_resource)
+
+ response = service.execute
+
+ expect(response.message).to eq('not valid')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/catalog/validate_resource_service_spec.rb b/spec/services/ci/catalog/validate_resource_service_spec.rb
new file mode 100644
index 00000000000..3bee37b7e55
--- /dev/null
+++ b/spec/services/ci/catalog/validate_resource_service_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::Catalog::ValidateResourceService, feature_category: :pipeline_composition do
+ describe '#execute' do
+ context 'with a project that has a README and a description' do
+ it 'is valid' do
+ project = create(:project, :repository, description: 'Component project')
+ response = described_class.new(project, project.default_branch).execute
+
+ expect(response).to be_success
+ end
+ end
+
+ context 'with a project that has neither a description nor a README' do
+ it 'is not valid' do
+ project = create(:project, :empty_repo)
+ project.repository.create_file(
+ project.creator,
+ 'ruby.rb',
+ 'I like this',
+ message: 'Ruby like this',
+ branch_name: 'master'
+ )
+ response = described_class.new(project, project.default_branch).execute
+
+ expect(response.message).to eq('Project must have a README , Project must have a description')
+ end
+ end
+
+ context 'with a project that has a description but not a README' do
+ it 'is not valid' do
+ project = create(:project, :empty_repo, description: 'project with no README')
+ project.repository.create_file(
+ project.creator,
+ 'text.txt',
+ 'I do not like this',
+ message: 'only text like text',
+ branch_name: 'master'
+ )
+ response = described_class.new(project, project.default_branch).execute
+
+ expect(response.message).to eq('Project must have a README')
+ end
+ end
+
+ context 'with a project that has a README and not a description' do
+ it 'is not valid' do
+ project = create(:project, :repository)
+ response = described_class.new(project, project.default_branch).execute
+
+ expect(response.message).to eq('Project must have a description')
+ end
+ end
+ end
+end
diff --git a/spec/services/import/github/cancel_project_import_service_spec.rb b/spec/services/import/github/cancel_project_import_service_spec.rb
index 17d5e0fa443..d8ea303fa50 100644
--- a/spec/services/import/github/cancel_project_import_service_spec.rb
+++ b/spec/services/import/github/cancel_project_import_service_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Import::Github::CancelProjectImportService, feature_category: :im
.to receive(:new)
.with(:github_importer, project)
.and_return(metrics_double)
- expect(metrics_double).to receive(:track_import_state)
+ expect(metrics_double).to receive(:track_canceled_import)
import_cancel.execute
end
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 039459e6cd2..74b80c4864c 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -1575,7 +1575,6 @@
- './ee/spec/lib/omni_auth/strategies/group_saml_spec.rb'
- './ee/spec/lib/omni_auth/strategies/kerberos_spec.rb'
- './ee/spec/lib/peek/views/elasticsearch_spec.rb'
-- './ee/spec/lib/sidebars/groups/menus/administration_menu_spec.rb'
- './ee/spec/lib/sidebars/groups/menus/analytics_menu_spec.rb'
- './ee/spec/lib/sidebars/groups/menus/epics_menu_spec.rb'
- './ee/spec/lib/sidebars/groups/menus/security_compliance_menu_spec.rb'
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 3af0b30bca0..8b03d8a2770 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -140,6 +140,7 @@ RSpec.shared_context 'group navbar structure' do
_('CI/CD'),
_('Applications'),
_('Packages and registries'),
+ s_('UsageQuota|Usage Quotas'),
_('Domain Verification')
]
}
@@ -154,15 +155,6 @@ RSpec.shared_context 'group navbar structure' do
}
end
- let(:administration_nav_item) do
- {
- nav_item: _('Administration'),
- nav_sub_items: [
- s_('UsageQuota|Usage Quotas')
- ]
- }
- end
-
let(:security_and_compliance_nav_item) do
{
nav_item: _('Security and Compliance'),
diff --git a/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb
new file mode 100644
index 00000000000..92c51a03b31
--- /dev/null
+++ b/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'User profile menu' do |title:, active_route:|
+ let_it_be(:current_user) { build(:user) }
+ let_it_be(:user) { build(:user) }
+
+ let(:context) { Sidebars::Context.new(current_user: current_user, container: user) }
+
+ subject { described_class.new(context) }
+
+ it 'does not contain any sub menu' do
+ expect(subject.has_items?).to be false
+ end
+
+ it 'renders the correct link' do
+ expect(subject.link).to match link
+ end
+
+ it 'renders the correct title' do
+ expect(subject.title).to eq title
+ end
+
+ it 'defines correct active route' do
+ expect(subject.active_routes[:path]).to be active_route
+ end
+
+ it 'renders if user is logged in' do
+ expect(subject.render?).to be true
+ end
+
+ [:blocked, :banned].each do |trait|
+ context "when viewed user is #{trait}" do
+ let_it_be(:viewed_user) { build(:user, trait) }
+ let(:context) { Sidebars::Context.new(current_user: user, container: viewed_user) }
+
+ context 'when user is not logged in' do
+ it 'is not allowed to view the menu item' do
+ expect(described_class.new(Sidebars::Context.new(current_user: nil,
+ container: viewed_user)).render?).to be false
+ end
+ end
+
+ context 'when current user has permission' do
+ before do
+ allow(Ability).to receive(:allowed?).with(user, :read_user_profile, viewed_user).and_return(true)
+ end
+
+ it 'is allowed to view the menu item' do
+ expect(described_class.new(context).render?).to be true
+ end
+ end
+
+ context 'when current user does not have permission' do
+ it 'is not allowed to view the menu item' do
+ expect(described_class.new(context).render?).to be false
+ end
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'Followers/followees counts' do |symbol|
+ let_it_be(:current_user) { build(:user) }
+ let_it_be(:user) { build(:user) }
+
+ let(:context) { Sidebars::Context.new(current_user: current_user, container: user) }
+
+ subject { described_class.new(context) }
+
+ context 'when there are items' do
+ before do
+ allow(user).to receive(symbol).and_return([1, 2])
+ end
+
+ it 'renders the pill' do
+ expect(subject.has_pill?).to be(true)
+ end
+
+ it 'returns the count' do
+ expect(subject.pill_count).to be(2)
+ end
+ end
+
+ context 'when there are no items' do
+ it 'does not render the pill' do
+ expect(subject.has_pill?).to be(false)
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 70556d0941b..a60bc36fced 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1157,10 +1157,10 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz#9be796d93ae27b636da32d960899a4912bca27a1"
integrity sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==
-"@eslint/eslintrc@^1.4.1":
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e"
- integrity sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==
+"@eslint/eslintrc@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.0.tgz#943309d8697c52fc82c076e90c1c74fbbe69dbff"
+ integrity sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
@@ -1172,6 +1172,11 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
+"@eslint/js@8.35.0":
+ version "8.35.0"
+ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.35.0.tgz#b7569632b0b788a0ca0e438235154e45d42813a7"
+ integrity sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==
+
"@gitlab/at.js@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@gitlab/at.js/-/at.js-1.5.7.tgz#1ee6f838cc4410a1d797770934df91d90df8179e"
@@ -5664,12 +5669,13 @@ eslint-visitor-keys@^3.3.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
-eslint@8.34.0:
- version "8.34.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.34.0.tgz#fe0ab0ef478104c1f9ebc5537e303d25a8fb22d6"
- integrity sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==
+eslint@8.35.0:
+ version "8.35.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.35.0.tgz#fffad7c7e326bae606f0e8f436a6158566d42323"
+ integrity sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==
dependencies:
- "@eslint/eslintrc" "^1.4.1"
+ "@eslint/eslintrc" "^2.0.0"
+ "@eslint/js" "8.35.0"
"@humanwhocodes/config-array" "^0.11.8"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
@@ -5683,7 +5689,7 @@ eslint@8.34.0:
eslint-utils "^3.0.0"
eslint-visitor-keys "^3.3.0"
espree "^9.4.0"
- esquery "^1.4.0"
+ esquery "^1.4.2"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
file-entry-cache "^6.0.1"
@@ -5730,6 +5736,13 @@ esquery@^1.4.0:
dependencies:
estraverse "^5.1.0"
+esquery@^1.4.2:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
+ integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
+ dependencies:
+ estraverse "^5.1.0"
+
esrecurse@^4.1.0, esrecurse@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"