Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-03-13 12:15:42 +00:00
parent e91ff9d11f
commit b4d5fdae42
93 changed files with 1428 additions and 151 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -0,0 +1,4 @@
---
# Grace period will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/395354
BackgroundMigration/MissingDictionaryFile:
Details: grace period

View File

@ -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'

View File

@ -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'

View File

@ -1 +1 @@
60b05a6155531cdfd5e51b2ed3c6e888f105048b
d1ce57834e67383564e6d7faf7827be3a7dd8afe

View File

@ -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"

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 } }

View File

@ -0,0 +1 @@
= render partial: 'shared/nav/sidebar', object: Sidebars::UserProfile::Panel.new(Sidebars::Context.new(current_user: current_user, container: @user))

View File

@ -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')

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
f9132e8d1d39307fc4f9ef17c6e044bab636d17ae7a7e5207f26ab3e38441638

View File

@ -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;

View File

@ -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:

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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 %>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 = {}

View File

@ -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 ""

View File

@ -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",

View File

@ -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?)

View File

@ -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

View File

@ -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);
});
});
});

View File

@ -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

View File

@ -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

View File

@ -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\.]+

View File

@ -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)

View File

@ -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

View File

@ -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,29 +41,6 @@ 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)
subject.track_failed_import
end
end
context 'when project is a github import' do
before do
project.import_type = 'github'
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(
category: :test_importer,
action: 'create',
@ -72,6 +48,8 @@ RSpec.describe Gitlab::Import::Metrics, :aggregate_failures do
project: project,
extra: { import_type: 'github', state: 'failed' }
)
subject.track_failed_import
end
end
@ -82,7 +60,9 @@ RSpec.describe Gitlab::Import::Metrics, :aggregate_failures do
end
it 'emits importer metrics' do
subject.track_import_state
expect(subject).to receive(:track_usage_event).with(:github_import_project_failure, project.id)
subject.track_failed_import
expect_snowplow_event(
category: :test_importer,
@ -96,28 +76,61 @@ RSpec.describe Gitlab::Import::Metrics, :aggregate_failures do
end
describe '#track_finished_import' do
before do
allow(Gitlab::Metrics).to receive(:histogram) { histogram }
end
context 'when project is a github import' do
before do
project.import_type = 'github'
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
expect(Gitlab::Metrics).to receive(:counter).with(
:test_importer_imported_projects_total,
'The number of imported projects'
)
it 'emits importer metrics' do
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(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)
expect(counter).to receive(:increment)
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: 'completed' }
)
expect(subject.duration).not_to be_nil
end
context 'when import is partially completed' do
before do
allow(project).to receive(:beautified_import_status_name).and_return('partially completed')
end
it 'emits snowplow metrics' do
expect(subject).to receive(:track_usage_event).with(:github_import_project_partially_completed, project.id)
subject.track_finished_import
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

View File

@ -704,7 +704,7 @@ project:
- project_registry
- packages
- package_files
- repository_files
- rpm_repository_files
- packages_cleanup_policy
- alerting_setting
- project_setting

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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'),

View File

@ -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

View File

@ -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"