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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-10-27 00:09:20 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-27 00:09:20 +0300
commitf825fd1d881ce077ad868a70fd8d7db6a49e4700 (patch)
treedaeae56475be62f02a6a00d7af2f2161a8dc6c50
parent51ba1dfa3bed95ec8c3fdee9b682a7941c4c34cc (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/graphql_shared/possible_types.json3
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue29
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb3
-rw-r--r--app/graphql/mutations/work_items/create.rb3
-rw-r--r--app/graphql/resolvers/work_items_resolver.rb3
-rw-r--r--app/graphql/types/work_items/widget_interface.rb5
-rw-r--r--app/graphql/types/work_items/widgets/milestone_input_type.rb17
-rw-r--r--app/graphql/types/work_items/widgets/milestone_type.rb23
-rw-r--r--app/helpers/application_settings_helper.rb2
-rw-r--r--app/models/application_setting.rb1
-rw-r--r--app/models/concerns/project_features_compatibility.rb4
-rw-r--r--app/models/namespace_setting.rb5
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_feature.rb1
-rw-r--r--app/models/wiki.rb16
-rw-r--r--app/models/work_items/type.rb6
-rw-r--r--app/models/work_items/widgets/milestone.rb9
-rw-r--r--app/policies/project_policy.rb9
-rw-r--r--app/services/work_items/create_service.rb7
-rw-r--r--app/services/work_items/widgets/milestone_service/base_service.rb39
-rw-r--r--app/services/work_items/widgets/milestone_service/create_service.rb13
-rw-r--r--app/services/work_items/widgets/milestone_service/update_service.rb13
-rw-r--r--db/migrate/20221015000511_add_email_confirmation_setting_to_application_settings.rb7
-rw-r--r--db/schema_migrations/202210150005111
-rw-r--r--db/structure.sql1
-rw-r--r--doc/api/graphql/reference/index.md24
-rw-r--r--lib/gitlab/git/repository.rb8
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb8
-rw-r--r--lib/gitlab/import_export/project/import_export.yml2
-rw-r--r--lib/sidebars/groups/menus/settings_menu.rb4
-rw-r--r--lib/sidebars/projects/menus/infrastructure_menu.rb10
-rw-r--r--locale/gitlab.pot15
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock4
-rw-r--r--rubocop/cop/gitlab/feature_available_usage.rb1
-rw-r--r--spec/controllers/projects_controller_spec.rb1
-rw-r--r--spec/factories/projects.rb1
-rw-r--r--spec/features/admin/admin_settings_spec.rb16
-rw-r--r--spec/frontend/admin/signup_restrictions/mock_data.js2
-rw-r--r--spec/lib/gitlab/database/migrations/runner_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb46
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb2
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/work_items/type_spec.rb3
-rw-r--r--spec/models/work_items/widgets/milestone_spec.rb27
-rw-r--r--spec/policies/project_policy_spec.rb68
-rw-r--r--spec/requests/api/graphql/mutations/work_items/create_spec.rb62
-rw-r--r--spec/requests/api/graphql/mutations/work_items/update_spec.rb90
-rw-r--r--spec/requests/api/graphql/project/work_items_spec.rb13
-rw-r--r--spec/requests/api/graphql/work_item_spec.rb34
-rw-r--r--spec/services/work_items/widgets/milestone_service/create_service_spec.rb28
-rw-r--r--spec/services/work_items/widgets/milestone_service/update_service_spec.rb58
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--spec/support/helpers/database/multiple_databases_helpers.rb (renamed from spec/support/database/multiple_databases.rb)75
-rw-r--r--spec/support/migration.rb36
-rw-r--r--spec/support/multiple_databases.rb25
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb49
-rw-r--r--spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb42
-rw-r--r--spec/support_specs/database/multiple_databases_helpers_spec.rb (renamed from spec/support_specs/database/multiple_databases_spec.rb)5
64 files changed, 880 insertions, 119 deletions
diff --git a/Gemfile b/Gemfile
index e54dc497090..056310e591e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -503,7 +503,7 @@ gem 'ssh_data', '~> 1.3'
gem 'spamcheck', '~> 1.0.0'
# Gitaly GRPC protocol definitions
-gem 'gitaly', '~> 15.4.0-rc2'
+gem 'gitaly', '~> 15.5.0'
# KAS GRPC protocol definitions
gem 'kas-grpc', '~> 0.0.2'
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 9455e6360e6..d3bd3b4045f 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -198,7 +198,7 @@
{"name":"gettext_i18n_rails","version":"1.8.0","platform":"ruby","checksum":"95e5cf8440b1e08705b27f2bccb56143272c5a7a0dabcf54ea1bd701140a496f"},
{"name":"gettext_i18n_rails_js","version":"1.3.0","platform":"ruby","checksum":"5d10afe4be3639bff78c50a56768c20f39aecdabc580c08aa45573911c2bd687"},
{"name":"git","version":"1.11.0","platform":"ruby","checksum":"7e95ba4da8298a0373ef1a6862aa22007d761f3c8274b675aa787966fecea0f1"},
-{"name":"gitaly","version":"15.4.0.pre.rc2","platform":"ruby","checksum":"48764528a730605a46f00cf86c7cfcb92d25f4f3d8cb9e09557baac3e9f3f8e3"},
+{"name":"gitaly","version":"15.5.0","platform":"ruby","checksum":"d85dd4890a1f0fd95f935c848bcedf03f19b78872f20f04b9811e602bea4ef42"},
{"name":"github-markup","version":"1.7.0","platform":"ruby","checksum":"97eb27c70662d9cc1d5997cd6c99832026fae5d4913b5dce1ce6c9f65078e69d"},
{"name":"gitlab","version":"4.16.1","platform":"ruby","checksum":"13fd7059cbdad5a1a21b15fa2cf9070b97d92e27f8c688581fe3d84dc038074f"},
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 877afac4509..82232f1c4b1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -547,7 +547,7 @@ GEM
rails (>= 3.2.0)
git (1.11.0)
rchardet (~> 1.8)
- gitaly (15.4.0.pre.rc2)
+ gitaly (15.5.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab (4.16.1)
@@ -1626,7 +1626,7 @@ DEPENDENCIES
gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly (~> 15.4.0.pre.rc2)
+ gitaly (~> 15.5.0)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 3.6.1)
diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json
index 545c150e536..1a949adc6a2 100644
--- a/app/assets/javascripts/graphql_shared/possible_types.json
+++ b/app/assets/javascripts/graphql_shared/possible_types.json
@@ -143,8 +143,9 @@
"WorkItemWidgetHierarchy",
"WorkItemWidgetIteration",
"WorkItemWidgetLabels",
+ "WorkItemWidgetMilestone",
"WorkItemWidgetStartAndDueDate",
"WorkItemWidgetStatus",
"WorkItemWidgetWeight"
]
-}
+} \ No newline at end of file
diff --git a/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue
index ccb449f96e1..95c1f575fe4 100644
--- a/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue
+++ b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue
@@ -44,6 +44,7 @@ export default {
'signupEnabled',
'requireAdminApprovalAfterUserSignup',
'sendUserConfirmationEmail',
+ 'emailConfirmationSetting',
'minimumPasswordLength',
'minimumPasswordLengthMin',
'minimumPasswordLengthMax',
@@ -66,6 +67,7 @@ export default {
signupEnabled: this.signupEnabled,
requireAdminApproval: this.requireAdminApprovalAfterUserSignup,
sendConfirmationEmail: this.sendUserConfirmationEmail,
+ emailConfirmationSetting: this.emailConfirmationSetting,
minimumPasswordLength: this.minimumPasswordLength,
minimumPasswordLengthMin: this.minimumPasswordLengthMin,
minimumPasswordLengthMax: this.minimumPasswordLengthMax,
@@ -199,6 +201,15 @@ export default {
signupEnabledLabel: s__('ApplicationSettings|Sign-up enabled'),
requireAdminApprovalLabel: s__('ApplicationSettings|Require admin approval for new sign-ups'),
sendConfirmationEmailLabel: s__('ApplicationSettings|Send confirmation email on sign-up'),
+ emailConfirmationSettingsLabel: s__('ApplicationSettings|Email confirmation settings'),
+ emailConfirmationSettingsOffLabel: s__('ApplicationSettings|Off'),
+ emailConfirmationSettingsOffHelpText: s__(
+ 'ApplicationSettings|New users can sign up without confirming their email address.',
+ ),
+ emailConfirmationSettingsHardLabel: s__('ApplicationSettings|Hard'),
+ emailConfirmationSettingsHardHelpText: s__(
+ 'ApplicationSettings|Send a confirmation email during sign up. New users must confirm their email address before they can log in.',
+ ),
minimumPasswordLengthLabel: s__(
'ApplicationSettings|Minimum password length (number of characters)',
),
@@ -276,6 +287,24 @@ export default {
:label="$options.i18n.sendConfirmationEmailLabel"
/>
+ <gl-form-group :label="$options.i18n.emailConfirmationSettingsLabel">
+ <gl-form-radio-group
+ v-model="form.emailConfirmationSetting"
+ name="application_setting[email_confirmation_setting]"
+ >
+ <gl-form-radio value="hard">
+ {{ $options.i18n.emailConfirmationSettingsHardLabel }}
+
+ <template #help> {{ $options.i18n.emailConfirmationSettingsHardHelpText }} </template>
+ </gl-form-radio>
+ <gl-form-radio value="off">
+ {{ $options.i18n.emailConfirmationSettingsOffLabel }}
+
+ <template #help> {{ $options.i18n.emailConfirmationSettingsOffHelpText }} </template>
+ </gl-form-radio>
+ </gl-form-radio-group>
+ </gl-form-group>
+
<gl-form-group
:label="$options.i18n.userCapLabel"
:description="$options.i18n.userCapDescription"
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 56eb2a6de56..2d2a732e85c 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -440,7 +440,7 @@ class ProjectsController < Projects::ApplicationController
def operations_feature_attributes
if Feature.enabled?(:split_operations_visibility_permissions, project)
%i[
- environments_access_level feature_flags_access_level monitor_access_level
+ environments_access_level feature_flags_access_level monitor_access_level infrastructure_access_level
]
else
%i[operations_access_level]
diff --git a/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
index e42e59de78f..6738f268e92 100644
--- a/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
+++ b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
@@ -33,6 +33,9 @@ module Mutations
argument :labels_widget, ::Types::WorkItems::Widgets::LabelsUpdateInputType,
required: false,
description: 'Input for labels widget.'
+ argument :milestone_widget, ::Types::WorkItems::Widgets::MilestoneInputType,
+ required: false,
+ description: 'Input for milestone widget.'
end
end
end
diff --git a/app/graphql/mutations/work_items/create.rb b/app/graphql/mutations/work_items/create.rb
index ece00e04ed9..a1af8064609 100644
--- a/app/graphql/mutations/work_items/create.rb
+++ b/app/graphql/mutations/work_items/create.rb
@@ -22,6 +22,9 @@ module Mutations
argument :hierarchy_widget, ::Types::WorkItems::Widgets::HierarchyCreateInputType,
required: false,
description: 'Input for hierarchy widget.'
+ argument :milestone_widget, ::Types::WorkItems::Widgets::MilestoneInputType,
+ required: false,
+ description: 'Input for milestone widget.'
argument :project_path, GraphQL::Types::ID,
required: true,
description: 'Full path of the project the work item is associated with.'
diff --git a/app/graphql/resolvers/work_items_resolver.rb b/app/graphql/resolvers/work_items_resolver.rb
index a4cbcc61ead..264c92a4443 100644
--- a/app/graphql/resolvers/work_items_resolver.rb
+++ b/app/graphql/resolvers/work_items_resolver.rb
@@ -55,7 +55,8 @@ module Resolvers
last_edited_by: :last_edited_by,
assignees: :assignees,
parent: :work_item_parent,
- labels: :labels
+ labels: :labels,
+ milestone: :milestone
}
end
diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb
index a3943361114..b85d0a23535 100644
--- a/app/graphql/types/work_items/widget_interface.rb
+++ b/app/graphql/types/work_items/widget_interface.rb
@@ -16,7 +16,8 @@ module Types
::Types::WorkItems::Widgets::HierarchyType,
::Types::WorkItems::Widgets::LabelsType,
::Types::WorkItems::Widgets::AssigneesType,
- ::Types::WorkItems::Widgets::StartAndDueDateType
+ ::Types::WorkItems::Widgets::StartAndDueDateType,
+ ::Types::WorkItems::Widgets::MilestoneType
].freeze
def self.ce_orphan_types
@@ -38,6 +39,8 @@ module Types
::Types::WorkItems::Widgets::LabelsType
when ::WorkItems::Widgets::StartAndDueDate
::Types::WorkItems::Widgets::StartAndDueDateType
+ when ::WorkItems::Widgets::Milestone
+ ::Types::WorkItems::Widgets::MilestoneType
else
raise "Unknown GraphQL type for widget #{object}"
end
diff --git a/app/graphql/types/work_items/widgets/milestone_input_type.rb b/app/graphql/types/work_items/widgets/milestone_input_type.rb
new file mode 100644
index 00000000000..996c782373f
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/milestone_input_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module Widgets
+ class MilestoneInputType < BaseInputObject
+ graphql_name 'WorkItemWidgetMilestoneInput'
+
+ argument :milestone_id,
+ Types::GlobalIDType[::Milestone],
+ required: :nullable,
+ prepare: ->(id, _) { id.model_id unless id.nil? },
+ description: 'Milestone to assign to the work item.'
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widgets/milestone_type.rb b/app/graphql/types/work_items/widgets/milestone_type.rb
new file mode 100644
index 00000000000..73318e58a00
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/milestone_type.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module Widgets
+ # Disabling widget level authorization as it might be too granular
+ # and we already authorize the parent work item
+ # rubocop:disable Graphql/AuthorizeTypes
+ class MilestoneType < BaseObject
+ graphql_name 'WorkItemWidgetMilestone'
+ description 'Represents a milestone widget'
+
+ implements Types::WorkItems::WidgetInterface
+
+ field :milestone,
+ ::Types::MilestoneType,
+ null: true,
+ description: 'Milestone of the work item.'
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 37196764974..9678d8bf2fd 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -241,6 +241,7 @@ module ApplicationSettingsHelper
:eks_access_key_id,
:eks_secret_access_key,
:email_author_in_body,
+ :email_confirmation_setting,
:enabled_git_access_protocol,
:enforce_terms,
:error_tracking_enabled,
@@ -544,6 +545,7 @@ module ApplicationSettingsHelper
signup_enabled: @application_setting[:signup_enabled].to_s,
require_admin_approval_after_user_signup: @application_setting[:require_admin_approval_after_user_signup].to_s,
send_user_confirmation_email: @application_setting[:send_user_confirmation_email].to_s,
+ email_confirmation_setting: @application_setting[:email_confirmation_setting].to_s,
minimum_password_length: @application_setting[:minimum_password_length],
minimum_password_length_min: ApplicationSetting::DEFAULT_MINIMUM_PASSWORD_LENGTH,
minimum_password_length_max: Devise.password_length.max,
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 173a897e4a3..a4a881b219b 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -20,6 +20,7 @@ class ApplicationSetting < ApplicationRecord
'Admin Area > Settings > General > Kroki'
enum whats_new_variant: { all_tiers: 0, current_tier: 1, disabled: 2 }, _prefix: true
+ enum email_confirmation_setting: { off: 0, soft: 1, hard: 2 }
add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption) ? :optional : :required }
add_authentication_token_field :health_check_access_token
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index 2976b6f02a7..d37f20e2e7c 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -110,6 +110,10 @@ module ProjectFeaturesCompatibility
write_feature_attribute_string(:releases_access_level, value)
end
+ def infrastructure_access_level=(value)
+ write_feature_attribute_string(:infrastructure_access_level, value)
+ end
+
# TODO: Remove this method after we drop support for project create/edit APIs to set the
# container_registry_enabled attribute. They can instead set the container_registry_access_level
# attribute.
diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb
index 6a87fba57ac..3e6371b0c4d 100644
--- a/app/models/namespace_setting.rb
+++ b/app/models/namespace_setting.rb
@@ -4,11 +4,6 @@ class NamespaceSetting < ApplicationRecord
include CascadingNamespaceSettingAttribute
include Sanitizable
include ChronicDurationAttribute
- include IgnorableColumns
-
- ignore_columns %i[exclude_from_free_user_cap include_for_free_user_cap_preview],
- remove_with: '15.5',
- remove_after: '2022-09-23'
cascading_attr :delayed_project_removal
diff --git a/app/models/project.rb b/app/models/project.rb
index 7accc208fc6..211e00eb7c6 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -451,7 +451,7 @@ class Project < ApplicationRecord
:metrics_dashboard_access_level, :analytics_access_level,
:operations_access_level, :security_and_compliance_access_level,
:container_registry_access_level, :environments_access_level, :feature_flags_access_level,
- :monitor_access_level, :releases_access_level,
+ :monitor_access_level, :releases_access_level, :infrastructure_access_level,
to: :project_feature, allow_nil: true
delegate :show_default_award_emojis, :show_default_award_emojis=,
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index dad8aaf0625..11f4a3f3b6f 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -25,6 +25,7 @@ class ProjectFeature < ApplicationRecord
environments
feature_flags
releases
+ infrastructure
].freeze
EXPORTABLE_FEATURES = (FEATURES - [:security_and_compliance, :pages]).freeze
diff --git a/app/models/wiki.rb b/app/models/wiki.rb
index 7d695537632..eb98bb67948 100644
--- a/app/models/wiki.rb
+++ b/app/models/wiki.rb
@@ -190,7 +190,7 @@ class Wiki
end
def empty?
- !repository_exists? || list_page_paths.empty?
+ !repository_exists? || list_page_paths(limit: 1).empty?
end
def exists?
@@ -207,9 +207,9 @@ class Wiki
#
# Returns an Array of GitLab WikiPage instances or an
# empty Array if this Wiki has no pages.
- def list_pages(limit: 0, direction: DIRECTION_ASC, load_content: false)
+ def list_pages(direction: DIRECTION_ASC, load_content: false, limit: 0, offset: 0)
create_wiki_repository unless repository_exists?
- list_pages_with_repository_rpcs(limit: limit, direction: direction, load_content: load_content)
+ list_pages_with_repository_rpcs(direction: direction, load_content: load_content, limit: limit, offset: offset)
end
def sidebar_entries(limit: Gitlab::WikiPages::MAX_SIDEBAR_PAGES, **options)
@@ -457,7 +457,7 @@ class Wiki
escaped_path = RE2::Regexp.escape(sluggified_title(title))
path_regexp = Gitlab::EncodingHelper.encode_utf8_no_detect("(?i)^#{escaped_path}\\.(#{file_extension_regexp})$")
- matched_files = repository.search_files_by_regexp(path_regexp, version)
+ matched_files = repository.search_files_by_regexp(path_regexp, version, limit: 1)
return if matched_files.blank?
Gitlab::EncodingHelper.encode_utf8_no_detect(matched_files.first)
@@ -509,15 +509,15 @@ class Wiki
path.sub(/\.[^.]+\z/, "")
end
- def list_page_paths
+ def list_page_paths(limit: 0, offset: 0)
return [] if repository.empty?
path_regexp = Gitlab::EncodingHelper.encode_utf8_no_detect("(?i)\\.(#{file_extension_regexp})$")
- repository.search_files_by_regexp(path_regexp, default_branch)
+ repository.search_files_by_regexp(path_regexp, default_branch, limit: limit, offset: offset)
end
- def list_pages_with_repository_rpcs(limit:, direction:, load_content:)
- paths = list_page_paths
+ def list_pages_with_repository_rpcs(direction:, load_content:, limit:, offset:)
+ paths = list_page_paths(limit: limit, offset: offset)
return [] if paths.empty?
pages = paths.map do |path|
diff --git a/app/models/work_items/type.rb b/app/models/work_items/type.rb
index 753fcbcb8f9..efffba62379 100644
--- a/app/models/work_items/type.rb
+++ b/app/models/work_items/type.rb
@@ -21,11 +21,13 @@ module WorkItems
}.freeze
WIDGETS_FOR_TYPE = {
- issue: [Widgets::Assignees, Widgets::Labels, Widgets::Description, Widgets::Hierarchy, Widgets::StartAndDueDate],
+ issue: [Widgets::Assignees, Widgets::Labels, Widgets::Description, Widgets::Hierarchy, Widgets::StartAndDueDate,
+ Widgets::Milestone],
incident: [Widgets::Description, Widgets::Hierarchy],
test_case: [Widgets::Description],
requirement: [Widgets::Description],
- task: [Widgets::Assignees, Widgets::Labels, Widgets::Description, Widgets::Hierarchy, Widgets::StartAndDueDate]
+ task: [Widgets::Assignees, Widgets::Labels, Widgets::Description, Widgets::Hierarchy, Widgets::StartAndDueDate,
+ Widgets::Milestone]
}.freeze
WI_TYPES_WITH_CREATED_HEADER = %w[issue incident].freeze
diff --git a/app/models/work_items/widgets/milestone.rb b/app/models/work_items/widgets/milestone.rb
new file mode 100644
index 00000000000..a3e610110f1
--- /dev/null
+++ b/app/models/work_items/widgets/milestone.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module WorkItems
+ module Widgets
+ class Milestone < Base
+ delegate :milestone, to: :work_item
+ end
+ end
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 77bdf9d62fc..832b78bb9c3 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -213,6 +213,7 @@ class ProjectPolicy < BasePolicy
environments
feature_flags
releases
+ infrastructure
]
features.each do |f|
@@ -409,6 +410,14 @@ class ProjectPolicy < BasePolicy
prevent(*create_read_update_admin_destroy(:alert_management_alert))
end
+ rule { split_operations_visibility_permissions & infrastructure_disabled }.policy do
+ prevent(*create_read_update_admin_destroy(:terraform_state))
+ prevent(*create_read_update_admin_destroy(:cluster))
+ prevent(:read_pod_logs)
+ prevent(:read_prometheus)
+ prevent(:admin_project_google_cloud)
+ end
+
rule { can?(:metrics_dashboard) }.policy do
enable :read_prometheus
enable :read_deployment
diff --git a/app/services/work_items/create_service.rb b/app/services/work_items/create_service.rb
index ebda043e873..87cc690d666 100644
--- a/app/services/work_items/create_service.rb
+++ b/app/services/work_items/create_service.rb
@@ -30,6 +30,13 @@ module WorkItems
error(e.message, :unprocessable_entity)
end
+ def before_create(work_item)
+ execute_widgets(work_item: work_item, callback: :before_create_callback,
+ widget_params: @widget_params)
+
+ super
+ end
+
def transaction_create(work_item)
super.tap do |save_result|
if save_result
diff --git a/app/services/work_items/widgets/milestone_service/base_service.rb b/app/services/work_items/widgets/milestone_service/base_service.rb
new file mode 100644
index 00000000000..f373e6daea3
--- /dev/null
+++ b/app/services/work_items/widgets/milestone_service/base_service.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module WorkItems
+ module Widgets
+ module MilestoneService
+ class BaseService < WorkItems::Widgets::BaseService
+ private
+
+ def handle_milestone_change(params:)
+ return unless params.present? && params.key?(:milestone_id)
+
+ unless has_permission?(:set_work_item_metadata)
+ params.delete(:milestone_id)
+ return
+ end
+
+ if params[:milestone_id].nil?
+ work_item.milestone = nil
+
+ return
+ end
+
+ project = work_item.project
+ milestone = MilestonesFinder.new({
+ project_ids: [project.id],
+ group_ids: project.group&.self_and_ancestors&.select(:id),
+ ids: [params[:milestone_id]]
+ }).execute.first
+
+ if milestone
+ work_item.milestone = milestone
+ else
+ params.delete(:milestone_id)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/work_items/widgets/milestone_service/create_service.rb b/app/services/work_items/widgets/milestone_service/create_service.rb
new file mode 100644
index 00000000000..e8d6bfe503c
--- /dev/null
+++ b/app/services/work_items/widgets/milestone_service/create_service.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module WorkItems
+ module Widgets
+ module MilestoneService
+ class CreateService < WorkItems::Widgets::MilestoneService::BaseService
+ def before_create_callback(params:)
+ handle_milestone_change(params: params)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/work_items/widgets/milestone_service/update_service.rb b/app/services/work_items/widgets/milestone_service/update_service.rb
new file mode 100644
index 00000000000..7ff0c2a5367
--- /dev/null
+++ b/app/services/work_items/widgets/milestone_service/update_service.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module WorkItems
+ module Widgets
+ module MilestoneService
+ class UpdateService < WorkItems::Widgets::MilestoneService::BaseService
+ def before_update_callback(params:)
+ handle_milestone_change(params: params)
+ end
+ end
+ end
+ end
+end
diff --git a/db/migrate/20221015000511_add_email_confirmation_setting_to_application_settings.rb b/db/migrate/20221015000511_add_email_confirmation_setting_to_application_settings.rb
new file mode 100644
index 00000000000..42fa4c1baf5
--- /dev/null
+++ b/db/migrate/20221015000511_add_email_confirmation_setting_to_application_settings.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddEmailConfirmationSettingToApplicationSettings < Gitlab::Database::Migration[2.0]
+ def change
+ add_column :application_settings, :email_confirmation_setting, :integer, limit: 2, default: 2
+ end
+end
diff --git a/db/schema_migrations/20221015000511 b/db/schema_migrations/20221015000511
new file mode 100644
index 00000000000..16845f8859c
--- /dev/null
+++ b/db/schema_migrations/20221015000511
@@ -0,0 +1 @@
+001b43cc0006b8f936310171ff2d12993eece1378f64945e6835728f540815ba \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 653f4ffae7c..570417ccbb6 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -11493,6 +11493,7 @@ CREATE TABLE application_settings (
password_expires_in_days integer DEFAULT 90 NOT NULL,
password_expires_notice_before_days integer DEFAULT 7 NOT NULL,
product_analytics_enabled boolean DEFAULT false NOT NULL,
+ email_confirmation_setting smallint DEFAULT 2,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 32b9c28b6e3..8555d8c6789 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -5774,6 +5774,7 @@ Input type: `WorkItemCreateInput`
| <a id="mutationworkitemcreateconfidential"></a>`confidential` | [`Boolean`](#boolean) | Sets the work item confidentiality. |
| <a id="mutationworkitemcreatedescription"></a>`description` | [`String`](#string) | Description of the work item. |
| <a id="mutationworkitemcreatehierarchywidget"></a>`hierarchyWidget` | [`WorkItemWidgetHierarchyCreateInput`](#workitemwidgethierarchycreateinput) | Input for hierarchy widget. |
+| <a id="mutationworkitemcreatemilestonewidget"></a>`milestoneWidget` | [`WorkItemWidgetMilestoneInput`](#workitemwidgetmilestoneinput) | Input for milestone widget. |
| <a id="mutationworkitemcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project the work item is associated with. |
| <a id="mutationworkitemcreatetitle"></a>`title` | [`String!`](#string) | Title of the work item. |
| <a id="mutationworkitemcreateworkitemtypeid"></a>`workItemTypeId` | [`WorkItemsTypeID!`](#workitemstypeid) | Global ID of a work item type. |
@@ -5887,6 +5888,7 @@ Input type: `WorkItemUpdateInput`
| <a id="mutationworkitemupdateid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
| <a id="mutationworkitemupdateiterationwidget"></a>`iterationWidget` | [`WorkItemWidgetIterationInput`](#workitemwidgetiterationinput) | Input for iteration widget. |
| <a id="mutationworkitemupdatelabelswidget"></a>`labelsWidget` | [`WorkItemWidgetLabelsUpdateInput`](#workitemwidgetlabelsupdateinput) | Input for labels widget. |
+| <a id="mutationworkitemupdatemilestonewidget"></a>`milestoneWidget` | [`WorkItemWidgetMilestoneInput`](#workitemwidgetmilestoneinput) | Input for milestone widget. |
| <a id="mutationworkitemupdatestartandduedatewidget"></a>`startAndDueDateWidget` | [`WorkItemWidgetStartAndDueDateUpdateInput`](#workitemwidgetstartandduedateupdateinput) | Input for start and due date widget. |
| <a id="mutationworkitemupdatestateevent"></a>`stateEvent` | [`WorkItemStateEvent`](#workitemstateevent) | Close or reopen a work item. |
| <a id="mutationworkitemupdatestatuswidget"></a>`statusWidget` | [`StatusInput`](#statusinput) | Input for status widget. |
@@ -19968,6 +19970,17 @@ Represents the labels widget.
| <a id="workitemwidgetlabelslabels"></a>`labels` | [`LabelConnection`](#labelconnection) | Labels assigned to the work item. (see [Connections](#connections)) |
| <a id="workitemwidgetlabelstype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
+### `WorkItemWidgetMilestone`
+
+Represents a milestone widget.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="workitemwidgetmilestonemilestone"></a>`milestone` | [`Milestone`](#milestone) | Milestone of the work item. |
+| <a id="workitemwidgetmilestonetype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
+
### `WorkItemWidgetStartAndDueDate`
Represents a start and due date widget.
@@ -22044,6 +22057,7 @@ Type of a work item widget.
| <a id="workitemwidgettypehierarchy"></a>`HIERARCHY` | Hierarchy widget. |
| <a id="workitemwidgettypeiteration"></a>`ITERATION` | Iteration widget. |
| <a id="workitemwidgettypelabels"></a>`LABELS` | Labels widget. |
+| <a id="workitemwidgettypemilestone"></a>`MILESTONE` | Milestone widget. |
| <a id="workitemwidgettypestart_and_due_date"></a>`START_AND_DUE_DATE` | Start And Due Date widget. |
| <a id="workitemwidgettypestatus"></a>`STATUS` | Status widget. |
| <a id="workitemwidgettypeweight"></a>`WEIGHT` | Weight widget. |
@@ -23314,6 +23328,7 @@ Implementations:
- [`WorkItemWidgetHierarchy`](#workitemwidgethierarchy)
- [`WorkItemWidgetIteration`](#workitemwidgetiteration)
- [`WorkItemWidgetLabels`](#workitemwidgetlabels)
+- [`WorkItemWidgetMilestone`](#workitemwidgetmilestone)
- [`WorkItemWidgetStartAndDueDate`](#workitemwidgetstartandduedate)
- [`WorkItemWidgetStatus`](#workitemwidgetstatus)
- [`WorkItemWidgetWeight`](#workitemwidgetweight)
@@ -23847,6 +23862,7 @@ A time-frame defined as a closed inclusive range of two dates.
| <a id="workitemupdatedtaskinputhierarchywidget"></a>`hierarchyWidget` | [`WorkItemWidgetHierarchyUpdateInput`](#workitemwidgethierarchyupdateinput) | Input for hierarchy widget. |
| <a id="workitemupdatedtaskinputid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
| <a id="workitemupdatedtaskinputlabelswidget"></a>`labelsWidget` | [`WorkItemWidgetLabelsUpdateInput`](#workitemwidgetlabelsupdateinput) | Input for labels widget. |
+| <a id="workitemupdatedtaskinputmilestonewidget"></a>`milestoneWidget` | [`WorkItemWidgetMilestoneInput`](#workitemwidgetmilestoneinput) | Input for milestone widget. |
| <a id="workitemupdatedtaskinputstartandduedatewidget"></a>`startAndDueDateWidget` | [`WorkItemWidgetStartAndDueDateUpdateInput`](#workitemwidgetstartandduedateupdateinput) | Input for start and due date widget. |
| <a id="workitemupdatedtaskinputstateevent"></a>`stateEvent` | [`WorkItemStateEvent`](#workitemstateevent) | Close or reopen a work item. |
| <a id="workitemupdatedtaskinputtitle"></a>`title` | [`String`](#string) | Title of the work item. |
@@ -23901,6 +23917,14 @@ A time-frame defined as a closed inclusive range of two dates.
| <a id="workitemwidgetlabelsupdateinputaddlabelids"></a>`addLabelIds` | [`[LabelID!]`](#labelid) | Global IDs of labels to be added to the work item. |
| <a id="workitemwidgetlabelsupdateinputremovelabelids"></a>`removeLabelIds` | [`[LabelID!]`](#labelid) | Global IDs of labels to be removed from the work item. |
+### `WorkItemWidgetMilestoneInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="workitemwidgetmilestoneinputmilestoneid"></a>`milestoneId` | [`MilestoneID`](#milestoneid) | Milestone to assign to the work item. |
+
### `WorkItemWidgetStartAndDueDateUpdateInput`
#### Arguments
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 9bbe17dcad1..a5eab9aae8c 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1054,19 +1054,19 @@ module Gitlab
end
end
- def search_files_by_name(query, ref)
+ def search_files_by_name(query, ref, limit: 0, offset: 0)
safe_query = query.sub(%r{^/*}, "")
ref ||= root_ref
return [] if empty? || safe_query.blank?
- gitaly_repository_client.search_files_by_name(ref, safe_query).map do |file|
+ gitaly_repository_client.search_files_by_name(ref, safe_query, limit: limit, offset: offset).map do |file|
Gitlab::EncodingHelper.encode_utf8(file)
end
end
- def search_files_by_regexp(filter, ref = 'HEAD')
- gitaly_repository_client.search_files_by_regexp(ref, filter).map do |file|
+ def search_files_by_regexp(filter, ref = 'HEAD', limit: 0, offset: 0)
+ gitaly_repository_client.search_files_by_regexp(ref, filter, limit: limit, offset: offset).map do |file|
Gitlab::EncodingHelper.encode_utf8(file)
end
end
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index f11437552e1..8934067551c 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -303,8 +303,8 @@ module Gitlab
GitalyClient.call(@storage, :repository_service, :get_raw_changes, request, timeout: GitalyClient.fast_timeout)
end
- def search_files_by_name(ref, query)
- request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: query)
+ def search_files_by_name(ref, query, limit: 0, offset: 0)
+ request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: query, limit: limit, offset: offset)
GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files)
end
@@ -314,8 +314,8 @@ module Gitlab
search_results_from_response(response, options)
end
- def search_files_by_regexp(ref, filter)
- request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: '.', filter: filter)
+ def search_files_by_regexp(ref, filter, limit: 0, offset: 0)
+ request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: '.', filter: filter, limit: limit, offset: offset)
GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files)
end
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index b2a593397e4..2d9c8d1108e 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -302,6 +302,7 @@ included_attributes:
- :environments_access_level
- :feature_flags_access_level
- :releases_access_level
+ - :infrastructure_access_level
prometheus_metrics:
- :created_at
- :updated_at
@@ -717,6 +718,7 @@ included_attributes:
- :environments_access_level
- :feature_flags_access_level
- :releases_access_level
+ - :infrastructure_access_level
- :allow_merge_on_skipped_pipeline
- :auto_devops_deploy_strategy
- :auto_devops_enabled
diff --git a/lib/sidebars/groups/menus/settings_menu.rb b/lib/sidebars/groups/menus/settings_menu.rb
index df170670aab..ede195a8e59 100644
--- a/lib/sidebars/groups/menus/settings_menu.rb
+++ b/lib/sidebars/groups/menus/settings_menu.rb
@@ -20,6 +20,10 @@ module Sidebars
# Push Rules are the only group setting that can also be edited by maintainers.
# Create an empty sub-menu here and EE adds Repository menu item (with only Push Rules).
return true
+ elsif Gitlab.ee? && can?(context.current_user, :read_billing, context.group)
+ # Billing is the only group setting that is visible to auditors.
+ # Create an empty sub-menu here and EE adds Settings menu item (with only Billing).
+ return true
end
false
diff --git a/lib/sidebars/projects/menus/infrastructure_menu.rb b/lib/sidebars/projects/menus/infrastructure_menu.rb
index 2181d89262b..a8ac3d10f83 100644
--- a/lib/sidebars/projects/menus/infrastructure_menu.rb
+++ b/lib/sidebars/projects/menus/infrastructure_menu.rb
@@ -6,7 +6,7 @@ module Sidebars
class InfrastructureMenu < ::Sidebars::Menu
override :configure_menu_items
def configure_menu_items
- return false unless context.project.feature_available?(:operations, context.current_user)
+ return false unless feature_enabled?
add_item(kubernetes_menu_item)
add_item(terraform_menu_item)
@@ -34,6 +34,14 @@ module Sidebars
private
+ def feature_enabled?
+ if ::Feature.enabled?(:split_operations_visibility_permissions, context.project)
+ context.project.feature_available?(:infrastructure, context.current_user)
+ else
+ context.project.feature_available?(:operations, context.current_user)
+ end
+ end
+
def kubernetes_menu_item
unless can?(context.current_user, :read_cluster, context.project)
return ::Sidebars::NilMenuItem.new(item_id: :kubernetes)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ed4ef44375e..4bbb0913375 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4717,6 +4717,9 @@ msgstr ""
msgid "ApplicationSettings|Domain denylist"
msgstr ""
+msgid "ApplicationSettings|Email confirmation settings"
+msgstr ""
+
msgid "ApplicationSettings|Email restrictions"
msgstr ""
@@ -4735,9 +4738,18 @@ msgstr ""
msgid "ApplicationSettings|Enter denylist manually"
msgstr ""
+msgid "ApplicationSettings|Hard"
+msgstr ""
+
msgid "ApplicationSettings|Minimum password length (number of characters)"
msgstr ""
+msgid "ApplicationSettings|New users can sign up without confirming their email address."
+msgstr ""
+
+msgid "ApplicationSettings|Off"
+msgstr ""
+
msgid "ApplicationSettings|Only users with e-mail addresses that match these domain(s) can sign up. Wildcards allowed. Use separate lines for multiple entries. Example: domain.com, *.domain.com"
msgstr ""
@@ -4765,6 +4777,9 @@ msgstr ""
msgid "ApplicationSettings|See %{linkStart}password policy guidelines%{linkEnd}."
msgstr ""
+msgid "ApplicationSettings|Send a confirmation email during sign up. New users must confirm their email address before they can log in."
+msgstr ""
+
msgid "ApplicationSettings|Send confirmation email on sign-up"
msgstr ""
diff --git a/qa/Gemfile b/qa/Gemfile
index 92023bee998..61904642387 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -2,7 +2,7 @@
source 'https://rubygems.org'
-gem 'gitlab-qa', '~> 8', '>= 8.8.0', require: 'gitlab/qa'
+gem 'gitlab-qa', '~> 8', '>= 8.9.0', require: 'gitlab/qa'
gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile
gem 'allure-rspec', '~> 2.18.0'
gem 'capybara', '~> 3.37.1'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 5e6b1531aff..f641e6e112b 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -100,7 +100,7 @@ GEM
gitlab (4.18.0)
httparty (~> 0.18)
terminal-table (>= 1.5.1)
- gitlab-qa (8.8.0)
+ gitlab-qa (8.9.0)
activesupport (~> 6.1)
gitlab (~> 4.18.0)
http (~> 5.0)
@@ -314,7 +314,7 @@ DEPENDENCIES
faraday-retry (~> 2.0)
fog-core (= 2.1.0)
fog-google (~> 1.19)
- gitlab-qa (~> 8, >= 8.8.0)
+ gitlab-qa (~> 8, >= 8.9.0)
influxdb-client (~> 2.7)
knapsack (~> 4.0)
nokogiri (~> 1.13, >= 1.13.9)
diff --git a/rubocop/cop/gitlab/feature_available_usage.rb b/rubocop/cop/gitlab/feature_available_usage.rb
index 4dba4baf1e7..fcf4992a19d 100644
--- a/rubocop/cop/gitlab/feature_available_usage.rb
+++ b/rubocop/cop/gitlab/feature_available_usage.rb
@@ -27,6 +27,7 @@ module RuboCop
environments
feature_flags
releases
+ infrastructure
].freeze
EE_FEATURES = %i[requirements].freeze
ALL_FEATURES = (FEATURES + EE_FEATURES).freeze
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index b5797e374f3..446e5e38865 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -921,6 +921,7 @@ RSpec.describe ProjectsController do
feature_flags_access_level
releases_access_level
monitor_access_level
+ infrastructure_access_level
]
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 61a3e5b7f79..3a25d179e84 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -41,6 +41,7 @@ FactoryBot.define do
environments_access_level { ProjectFeature::ENABLED }
feature_flags_access_level { ProjectFeature::ENABLED }
releases_access_level { ProjectFeature::ENABLED }
+ infrastructure_access_level { ProjectFeature::ENABLED }
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 3474d4c3907..25ea8743b53 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -205,6 +205,22 @@ RSpec.describe 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
end
end
+
+ context 'Email confirmation settings' do
+ it "is set to 'hard' by default" do
+ expect(current_settings.email_confirmation_setting).to eq('hard')
+ end
+
+ it 'changes the setting', :js do
+ page.within('.as-signup') do
+ choose 'Off'
+ click_button 'Save changes'
+ end
+
+ expect(current_settings.email_confirmation_setting).to eq('off')
+ expect(page).to have_content "Application settings saved successfully"
+ end
+ end
end
it 'change Sign-in restrictions' do
diff --git a/spec/frontend/admin/signup_restrictions/mock_data.js b/spec/frontend/admin/signup_restrictions/mock_data.js
index 9e001e122a4..62e931d9163 100644
--- a/spec/frontend/admin/signup_restrictions/mock_data.js
+++ b/spec/frontend/admin/signup_restrictions/mock_data.js
@@ -4,6 +4,7 @@ export const rawMockData = {
signupEnabled: 'true',
requireAdminApprovalAfterUserSignup: 'true',
sendUserConfirmationEmail: 'true',
+ emailConfirmationSetting: 'hard',
minimumPasswordLength: '8',
minimumPasswordLengthMin: '3',
minimumPasswordLengthMax: '10',
@@ -30,6 +31,7 @@ export const mockData = {
signupEnabled: true,
requireAdminApprovalAfterUserSignup: true,
sendUserConfirmationEmail: true,
+ emailConfirmationSetting: 'hard',
minimumPasswordLength: '8',
minimumPasswordLengthMin: '3',
minimumPasswordLengthMax: '10',
diff --git a/spec/lib/gitlab/database/migrations/runner_spec.rb b/spec/lib/gitlab/database/migrations/runner_spec.rb
index f364ebfa522..6283cb58b47 100644
--- a/spec/lib/gitlab/database/migrations/runner_spec.rb
+++ b/spec/lib/gitlab/database/migrations/runner_spec.rb
@@ -2,8 +2,6 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::Migrations::Runner, :reestablished_active_record_base do
- include Database::MultipleDatabases
-
let(:base_result_dir) { Pathname.new(Dir.mktmpdir) }
let(:migration_runs) { [] } # This list gets populated as the runner tries to run migrations
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 1ee2fbcec29..64fed73303f 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -592,6 +592,7 @@ ProjectFeature:
- feature_flags_access_level
- releases_access_level
- monitor_access_level
+- infrastructure_access_level
- created_at
- updated_at
ProtectedBranch::MergeAccessLevel:
diff --git a/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb b/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb
index 2da7d324708..64408ac3b88 100644
--- a/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb
@@ -23,6 +23,52 @@ RSpec.describe Sidebars::Projects::Menus::InfrastructureMenu do
expect(subject.render?).to be true
end
end
+
+ describe 'behavior based on access level setting' do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:project) { create(:project) }
+ let(:enabled) { Featurable::PRIVATE }
+ let(:disabled) { Featurable::DISABLED }
+
+ where(:operations_access_level, :infrastructure_access_level, :render) do
+ ref(:disabled) | ref(:enabled) | true
+ ref(:disabled) | ref(:disabled) | false
+ ref(:enabled) | ref(:enabled) | true
+ ref(:enabled) | ref(:disabled) | false
+ end
+
+ with_them do
+ it 'renders based on the infrastructure access level' do
+ project.project_feature.update!(operations_access_level: operations_access_level)
+ project.project_feature.update!(infrastructure_access_level: infrastructure_access_level)
+
+ expect(subject.render?).to be render
+ end
+ end
+
+ context 'when `split_operations_visibility_permissions` feature flag is disabled' do
+ before do
+ stub_feature_flags(split_operations_visibility_permissions: false)
+ end
+
+ where(:operations_access_level, :infrastructure_access_level, :render) do
+ ref(:disabled) | ref(:enabled) | false
+ ref(:disabled) | ref(:disabled) | false
+ ref(:enabled) | ref(:enabled) | true
+ ref(:enabled) | ref(:disabled) | true
+ end
+
+ with_them do
+ it 'renders based on the operations access level' do
+ project.project_feature.update!(operations_access_level: operations_access_level)
+ project.project_feature.update!(infrastructure_access_level: infrastructure_access_level)
+
+ expect(subject.render?).to be render
+ end
+ end
+ end
+ end
end
describe '#link' do
diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
index 89f34834aa4..f168bedc8eb 100644
--- a/spec/models/concerns/project_features_compatibility_spec.rb
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe ProjectFeaturesCompatibility do
let(:features) do
features_enabled + %w(
repository pages operations container_registry package_registry environments feature_flags releases
- monitor
+ monitor infrastructure
)
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index bf98566fdcc..88d96a812e0 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -862,6 +862,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to delegate_method(:environments_access_level).to(:project_feature) }
it { is_expected.to delegate_method(:feature_flags_access_level).to(:project_feature) }
it { is_expected.to delegate_method(:releases_access_level).to(:project_feature) }
+ it { is_expected.to delegate_method(:infrastructure_access_level).to(:project_feature) }
it { is_expected.to delegate_method(:maven_package_requests_forwarding).to(:namespace) }
it { is_expected.to delegate_method(:pypi_package_requests_forwarding).to(:namespace) }
it { is_expected.to delegate_method(:npm_package_requests_forwarding).to(:namespace) }
diff --git a/spec/models/work_items/type_spec.rb b/spec/models/work_items/type_spec.rb
index e41df7f0f61..aa18e00f63b 100644
--- a/spec/models/work_items/type_spec.rb
+++ b/spec/models/work_items/type_spec.rb
@@ -69,7 +69,8 @@ RSpec.describe WorkItems::Type do
::WorkItems::Widgets::Hierarchy,
::WorkItems::Widgets::Labels,
::WorkItems::Widgets::Assignees,
- ::WorkItems::Widgets::StartAndDueDate
+ ::WorkItems::Widgets::StartAndDueDate,
+ ::WorkItems::Widgets::Milestone
)
end
end
diff --git a/spec/models/work_items/widgets/milestone_spec.rb b/spec/models/work_items/widgets/milestone_spec.rb
new file mode 100644
index 00000000000..7b2d661df29
--- /dev/null
+++ b/spec/models/work_items/widgets/milestone_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WorkItems::Widgets::Milestone do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:milestone) { create(:milestone, project: project) }
+ let_it_be(:work_item) { create(:work_item, :issue, project: project, milestone: milestone) }
+
+ describe '.type' do
+ subject { described_class.type }
+
+ it { is_expected.to eq(:milestone) }
+ end
+
+ describe '#type' do
+ subject { described_class.new(work_item).type }
+
+ it { is_expected.to eq(:milestone) }
+ end
+
+ describe '#milestone' do
+ subject { described_class.new(work_item).milestone }
+
+ it { is_expected.to eq(work_item.milestone) }
+ end
+end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 40ee2e662b2..bf361c00374 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -2299,6 +2299,74 @@ RSpec.describe ProjectPolicy do
end
end
+ describe 'infrastructure feature' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:guest_permissions) { [] }
+
+ let(:developer_permissions) do
+ guest_permissions + [:read_terraform_state, :read_pod_logs, :read_prometheus]
+ end
+
+ let(:maintainer_permissions) do
+ developer_permissions + [:create_cluster, :read_cluster, :update_cluster, :admin_cluster, :admin_terraform_state, :admin_project_google_cloud]
+ end
+
+ where(:project_visibility, :access_level, :role, :allowed) do
+ :public | ProjectFeature::ENABLED | :maintainer | true
+ :public | ProjectFeature::ENABLED | :developer | true
+ :public | ProjectFeature::ENABLED | :guest | true
+ :public | ProjectFeature::ENABLED | :anonymous | true
+ :public | ProjectFeature::PRIVATE | :maintainer | true
+ :public | ProjectFeature::PRIVATE | :developer | true
+ :public | ProjectFeature::PRIVATE | :guest | true
+ :public | ProjectFeature::PRIVATE | :anonymous | false
+ :public | ProjectFeature::DISABLED | :maintainer | false
+ :public | ProjectFeature::DISABLED | :developer | false
+ :public | ProjectFeature::DISABLED | :guest | false
+ :public | ProjectFeature::DISABLED | :anonymous | false
+ :internal | ProjectFeature::ENABLED | :maintainer | true
+ :internal | ProjectFeature::ENABLED | :developer | true
+ :internal | ProjectFeature::ENABLED | :guest | true
+ :internal | ProjectFeature::ENABLED | :anonymous | false
+ :internal | ProjectFeature::PRIVATE | :maintainer | true
+ :internal | ProjectFeature::PRIVATE | :developer | true
+ :internal | ProjectFeature::PRIVATE | :guest | true
+ :internal | ProjectFeature::PRIVATE | :anonymous | false
+ :internal | ProjectFeature::DISABLED | :maintainer | false
+ :internal | ProjectFeature::DISABLED | :developer | false
+ :internal | ProjectFeature::DISABLED | :guest | false
+ :internal | ProjectFeature::DISABLED | :anonymous | false
+ :private | ProjectFeature::ENABLED | :maintainer | true
+ :private | ProjectFeature::ENABLED | :developer | true
+ :private | ProjectFeature::ENABLED | :guest | true
+ :private | ProjectFeature::ENABLED | :anonymous | false
+ :private | ProjectFeature::PRIVATE | :maintainer | true
+ :private | ProjectFeature::PRIVATE | :developer | true
+ :private | ProjectFeature::PRIVATE | :guest | true
+ :private | ProjectFeature::PRIVATE | :anonymous | false
+ :private | ProjectFeature::DISABLED | :maintainer | false
+ :private | ProjectFeature::DISABLED | :developer | false
+ :private | ProjectFeature::DISABLED | :guest | false
+ :private | ProjectFeature::DISABLED | :anonymous | false
+ end
+
+ with_them do
+ let(:current_user) { user_subject(role) }
+ let(:project) { project_subject(project_visibility) }
+
+ it 'allows/disallows the abilities based on the infrastructure access level' do
+ project.project_feature.update!(infrastructure_access_level: access_level)
+
+ if allowed
+ expect_allowed(*permissions_abilities(role))
+ else
+ expect_disallowed(*permissions_abilities(role))
+ end
+ end
+ end
+ end
+
describe 'access_security_and_compliance' do
context 'when the "Security & Compliance" is enabled' do
before do
diff --git a/spec/requests/api/graphql/mutations/work_items/create_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
index 8233821053f..2fd4c5e7b35 100644
--- a/spec/requests/api/graphql/mutations/work_items/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
@@ -154,6 +154,68 @@ RSpec.describe 'Create a work item' do
end
end
+ context 'with milestone widget input' do
+ let(:widgets_response) { mutation_response['workItem']['widgets'] }
+ let(:fields) do
+ <<~FIELDS
+ workItem {
+ widgets {
+ type
+ ... on WorkItemWidgetMilestone {
+ milestone {
+ id
+ }
+ }
+ }
+ }
+ errors
+ FIELDS
+ end
+
+ let(:mutation) { graphql_mutation(:workItemCreate, input.merge('projectPath' => project.full_path), fields) }
+
+ context 'when setting milestone on work item creation' do
+ let_it_be(:project_milestone) { create(:milestone, project: project) }
+ let_it_be(:group_milestone) { create(:milestone, project: project) }
+
+ let(:input) do
+ {
+ title: 'some WI',
+ workItemTypeId: WorkItems::Type.default_by_type(:task).to_global_id.to_s,
+ milestoneWidget: { 'milestoneId' => milestone.to_global_id.to_s }
+ }
+ end
+
+ shared_examples "work item's milestone is set" do
+ it "sets the work item's milestone" do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change(WorkItem, :count).by(1)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(widgets_response).to include(
+ {
+ 'type' => 'MILESTONE',
+ 'milestone' => { 'id' => milestone.to_global_id.to_s }
+ }
+ )
+ end
+ end
+
+ context 'when assigning a project milestone' do
+ it_behaves_like "work item's milestone is set" do
+ let(:milestone) { project_milestone }
+ end
+ end
+
+ context 'when assigning a group milestone' do
+ it_behaves_like "work item's milestone is set" do
+ let(:milestone) { group_milestone }
+ end
+ end
+ end
+ end
+
context 'when the work_items feature flag is disabled' do
before do
stub_feature_flags(work_items: false)
diff --git a/spec/requests/api/graphql/mutations/work_items/update_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_spec.rb
index 6b0129c457f..a6d4f518f5b 100644
--- a/spec/requests/api/graphql/mutations/work_items/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/update_spec.rb
@@ -5,8 +5,11 @@ require 'spec_helper'
RSpec.describe 'Update a work item' do
include GraphqlHelpers
- let_it_be(:project) { create(:project) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
+ let_it_be(:reporter) { create(:user).tap { |user| project.add_reporter(user) } }
+ let_it_be(:guest) { create(:user).tap { |user| project.add_guest(user) } }
let_it_be(:work_item, refind: true) { create(:work_item, project: project) }
let(:work_item_event) { 'CLOSE' }
@@ -543,6 +546,91 @@ RSpec.describe 'Update a work item' do
end
end
+ context 'when updating milestone' do
+ let_it_be(:project_milestone) { create(:milestone, project: project) }
+ let_it_be(:group_milestone) { create(:milestone, project: project) }
+
+ let(:input) { { 'milestoneWidget' => { 'milestoneId' => new_milestone&.to_global_id&.to_s } } }
+
+ let(:fields) do
+ <<~FIELDS
+ workItem {
+ widgets {
+ type
+ ... on WorkItemWidgetMilestone {
+ milestone {
+ id
+ }
+ }
+ }
+ }
+ errors
+ FIELDS
+ end
+
+ shared_examples "work item's milestone is updated" do
+ it "updates the work item's milestone" do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ work_item.reload
+ end.to change(work_item, :milestone).from(old_milestone).to(new_milestone)
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+ end
+
+ shared_examples "work item's milestone is not updated" do
+ it "ignores the update request" do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ work_item.reload
+ end.to not_change(work_item, :milestone)
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+ end
+
+ context 'when user cannot set work item metadata' do
+ let(:current_user) { guest }
+ let(:old_milestone) { nil }
+
+ it_behaves_like "work item's milestone is not updated" do
+ let(:new_milestone) { project_milestone }
+ end
+ end
+
+ context 'when user can set work item metadata' do
+ let(:current_user) { reporter }
+
+ context 'when assigning a project milestone' do
+ it_behaves_like "work item's milestone is updated" do
+ let(:old_milestone) { nil }
+ let(:new_milestone) { project_milestone }
+ end
+ end
+
+ context 'when assigning a group milestone' do
+ it_behaves_like "work item's milestone is updated" do
+ let(:old_milestone) { nil }
+ let(:new_milestone) { group_milestone }
+ end
+ end
+
+ context "when unsetting the work item's milestone" do
+ it_behaves_like "work item's milestone is updated" do
+ let(:old_milestone) { group_milestone }
+ let(:new_milestone) { nil }
+
+ before do
+ work_item.update!(milestone: old_milestone)
+ end
+ end
+ end
+ end
+ end
+
context 'when unsupported widget input is sent' do
let_it_be(:test_case) { create(:work_item_type, :default, :test_case, name: 'some_test_case_name') }
let_it_be(:work_item) { create(:work_item, work_item_type: test_case, project: project) }
diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb
index e82f6ad24a2..3c41950d578 100644
--- a/spec/requests/api/graphql/project/work_items_spec.rb
+++ b/spec/requests/api/graphql/project/work_items_spec.rb
@@ -10,6 +10,8 @@ RSpec.describe 'getting a work item list for a project' do
let_it_be(:current_user) { create(:user) }
let_it_be(:label1) { create(:label, project: project) }
let_it_be(:label2) { create(:label, project: project) }
+ let_it_be(:milestone1) { create(:milestone, project: project) }
+ let_it_be(:milestone2) { create(:milestone, project: project) }
let_it_be(:item1) { create(:work_item, project: project, discussion_locked: true, title: 'item1', labels: [label1]) }
let_it_be(:item2) do
@@ -19,7 +21,8 @@ RSpec.describe 'getting a work item list for a project' do
title: 'item2',
last_edited_by: current_user,
last_edited_at: 1.day.ago,
- labels: [label2]
+ labels: [label2],
+ milestone: milestone1
)
end
@@ -55,7 +58,8 @@ RSpec.describe 'getting a work item list for a project' do
:last_edited_by_user,
last_edited_at: 1.week.ago,
project: project,
- labels: [label1, label2]
+ labels: [label1, label2],
+ milestone: milestone2
)
expect_graphql_errors_to_be_empty
@@ -94,6 +98,11 @@ RSpec.describe 'getting a work item list for a project' do
labels { nodes { id } }
allowsScopedLabels
}
+ ... on WorkItemWidgetMilestone {
+ milestone {
+ id
+ }
+ }
}
}
GRAPHQL
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index 2105e479ed2..08333824c05 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -298,6 +298,40 @@ RSpec.describe 'Query.work_item(id)' do
)
end
end
+
+ describe 'milestone widget' do
+ let_it_be(:milestone) { create(:milestone, project: project) }
+
+ let(:work_item) { create(:work_item, project: project, milestone: milestone) }
+
+ let(:work_item_fields) do
+ <<~GRAPHQL
+ id
+ widgets {
+ type
+ ... on WorkItemWidgetMilestone {
+ milestone {
+ id
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ it 'returns widget information' do
+ expect(work_item_data).to include(
+ 'id' => work_item.to_gid.to_s,
+ 'widgets' => include(
+ hash_including(
+ 'type' => 'MILESTONE',
+ 'milestone' => {
+ 'id' => work_item.milestone.to_gid.to_s
+ }
+ )
+ )
+ )
+ end
+ end
end
context 'when an Issue Global ID is provided' do
diff --git a/spec/services/work_items/widgets/milestone_service/create_service_spec.rb b/spec/services/work_items/widgets/milestone_service/create_service_spec.rb
new file mode 100644
index 00000000000..3f90784b703
--- /dev/null
+++ b/spec/services/work_items/widgets/milestone_service/create_service_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WorkItems::Widgets::MilestoneService::CreateService do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :private, group: group) }
+ let_it_be(:project_milestone) { create(:milestone, project: project) }
+ let_it_be(:group_milestone) { create(:milestone, group: group) }
+ let_it_be(:guest) { create(:user) }
+
+ let(:current_user) { guest }
+ let(:work_item) { build(:work_item, project: project, updated_at: 1.day.ago) }
+ let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::Milestone) } }
+ let(:service) { described_class.new(widget: widget, current_user: current_user) }
+
+ before do
+ project.add_guest(guest)
+ end
+
+ describe '#before_create_callback' do
+ it_behaves_like "setting work item's milestone" do
+ subject(:execute_callback) do
+ service.before_create_callback(params: params)
+ end
+ end
+ end
+end
diff --git a/spec/services/work_items/widgets/milestone_service/update_service_spec.rb b/spec/services/work_items/widgets/milestone_service/update_service_spec.rb
new file mode 100644
index 00000000000..f3a7fc156b9
--- /dev/null
+++ b/spec/services/work_items/widgets/milestone_service/update_service_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WorkItems::Widgets::MilestoneService::UpdateService do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :private, group: group) }
+ let_it_be(:project_milestone) { create(:milestone, project: project) }
+ let_it_be(:group_milestone) { create(:milestone, group: group) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+
+ let(:work_item) { create(:work_item, project: project, updated_at: 1.day.ago) }
+ let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::Milestone) } }
+ let(:service) { described_class.new(widget: widget, current_user: current_user) }
+
+ before do
+ project.add_reporter(reporter)
+ project.add_guest(guest)
+ end
+
+ describe '#before_update_callback' do
+ context 'when current user is not allowed to set work item metadata' do
+ let(:current_user) { guest }
+ let(:params) { { milestone_id: group_milestone.id } }
+
+ it "does not set the work item's milestone" do
+ expect { service.before_update_callback(params: params) }
+ .to not_change(work_item, :milestone)
+ end
+ end
+
+ context "when current user is allowed to set work item metadata" do
+ let(:current_user) { reporter }
+
+ it_behaves_like "setting work item's milestone" do
+ subject(:execute_callback) do
+ service.before_update_callback(params: params)
+ end
+ end
+
+ context 'when unsetting a milestone' do
+ let(:params) { { milestone_id: nil } }
+
+ before do
+ work_item.update!(milestone: project_milestone)
+ end
+
+ it "sets the work item's milestone" do
+ expect { service.before_update_callback(params: params) }
+ .to change(work_item, :milestone)
+ .from(project_milestone)
+ .to(nil)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 8a1fa486bde..6761e100cfe 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -145,7 +145,7 @@ RSpec.configure do |config|
config.include NextInstanceOf
config.include TestEnv
config.include FileReadHelpers
- config.include Database::MultipleDatabases
+ config.include Database::MultipleDatabasesHelpers
config.include Database::WithoutCheckConstraint
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::ControllerHelpers, type: :view
diff --git a/spec/support/database/multiple_databases.rb b/spec/support/helpers/database/multiple_databases_helpers.rb
index b6341c2caec..16f5168ca29 100644
--- a/spec/support/database/multiple_databases.rb
+++ b/spec/support/helpers/database/multiple_databases_helpers.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Database
- module MultipleDatabases
+ module MultipleDatabasesHelpers
def skip_if_multiple_databases_not_setup
skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
end
@@ -52,17 +52,17 @@ module Database
#
# rubocop:disable Database/MultipleDatabases
def with_reestablished_active_record_base(reconnect: true)
- connection_classes = ActiveRecord::Base.connection_handler.connection_pool_names.map(&:constantize).to_h do |klass|
- [klass, klass.connection_db_config]
- end
+ connection_classes = ActiveRecord::Base
+ .connection_handler
+ .connection_pool_names
+ .map(&:constantize)
+ .index_with(&:connection_db_config)
original_handler = ActiveRecord::Base.connection_handler
new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
ActiveRecord::Base.connection_handler = new_handler
- if reconnect
- connection_classes.each { |klass, db_config| klass.establish_connection(db_config) }
- end
+ connection_classes.each { |klass, db_config| klass.establish_connection(db_config) } if reconnect
yield
ensure
@@ -95,9 +95,12 @@ module Database
module ActiveRecordBaseEstablishConnection
def establish_connection(*args)
# rubocop:disable Database/MultipleDatabases
- if connected? && connection&.transaction_open? && ActiveRecord::Base.connection_handler == ActiveRecord::Base.default_connection_handler
- raise "Cannot re-establish '#{self}.establish_connection' within an open transaction (#{connection&.open_transactions.to_i}). " \
- "Use `with_reestablished_active_record_base` instead or add `:reestablished_active_record_base` to rspec context."
+ if connected? &&
+ connection&.transaction_open? &&
+ ActiveRecord::Base.connection_handler == ActiveRecord::Base.default_connection_handler
+ raise "Cannot re-establish '#{self}.establish_connection' within an open transaction " \
+ "(#{connection&.open_transactions.to_i}). Use `with_reestablished_active_record_base` " \
+ "instead or add `:reestablished_active_record_base` to rspec context."
end
# rubocop:enable Database/MultipleDatabases
@@ -106,56 +109,4 @@ module Database
end
end
-RSpec.configure do |config|
- # Ensure database versions are memoized to prevent query counts from
- # being affected by version checks. Note that
- # Gitlab::Database.check_postgres_version_and_print_warning is called
- # at startup, but that generates its own
- # `Gitlab::Database::Reflection` so the result is not memoized by
- # callers of `ApplicationRecord.database.version`, such as
- # `Gitlab::Database::AsWithMaterialized.materialized_supported?`.
- # TODO This can be removed once https://gitlab.com/gitlab-org/gitlab/-/issues/325639 is completed.
- [ApplicationRecord, ::Ci::ApplicationRecord].each { |record| record.database.version }
-
- config.around(:each, :reestablished_active_record_base) do |example|
- with_reestablished_active_record_base(reconnect: example.metadata.fetch(:reconnect, true)) do
- example.run
- end
- end
-
- config.around(:each, :add_ci_connection) do |example|
- with_added_ci_connection do
- example.run
- end
- end
-
- config.append_after(:context, :migration) do
- recreate_databases_and_seed_if_needed || ensure_schema_and_empty_tables
- end
-
- config.around(:each, :migration) do |example|
- self.class.use_transactional_tests = false
-
- migration_schema = example.metadata[:migration]
- migration_schema = :gitlab_main if migration_schema == true
- base_model = Gitlab::Database.schemas_to_base_models.fetch(migration_schema).first
-
- # Migration require an `ActiveRecord::Base` to point to desired database
- if base_model != ActiveRecord::Base
- with_reestablished_active_record_base do
- reconfigure_db_connection(
- model: ActiveRecord::Base,
- config_model: base_model
- )
-
- example.run
- end
- else
- example.run
- end
-
- self.class.use_transactional_tests = true
- end
-end
-
ActiveRecord::Base.singleton_class.prepend(::Database::ActiveRecordBaseEstablishConnection) # rubocop:disable Database/MultipleDatabases
diff --git a/spec/support/migration.rb b/spec/support/migration.rb
index 490aa836d74..2a69630a29a 100644
--- a/spec/support/migration.rb
+++ b/spec/support/migration.rb
@@ -16,14 +16,42 @@ RSpec.configure do |config|
schema_migrate_down!
end
+ config.after(:context, :migration) do
+ Gitlab::CurrentSettings.clear_in_memory_application_settings!
+ end
+
+ config.append_after(:context, :migration) do
+ recreate_databases_and_seed_if_needed || ensure_schema_and_empty_tables
+ end
+
+ config.around(:each, :migration) do |example|
+ self.class.use_transactional_tests = false
+
+ migration_schema = example.metadata[:migration]
+ migration_schema = :gitlab_main if migration_schema == true
+ base_model = Gitlab::Database.schemas_to_base_models.fetch(migration_schema).first
+
+ # Migration require an `ActiveRecord::Base` to point to desired database
+ if base_model != ActiveRecord::Base
+ with_reestablished_active_record_base do
+ reconfigure_db_connection(
+ model: ActiveRecord::Base,
+ config_model: base_model
+ )
+
+ example.run
+ end
+ else
+ example.run
+ end
+
+ self.class.use_transactional_tests = true
+ end
+
# Each example may call `migrate!`, so we must ensure we are migrated down every time
config.before(:each, :migration) do
use_fake_application_settings
schema_migrate_down!
end
-
- config.after(:context, :migration) do
- Gitlab::CurrentSettings.clear_in_memory_application_settings!
- end
end
diff --git a/spec/support/multiple_databases.rb b/spec/support/multiple_databases.rb
new file mode 100644
index 00000000000..616cf00269c
--- /dev/null
+++ b/spec/support/multiple_databases.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ # Ensure database versions are memoized to prevent query counts from
+ # being affected by version checks. Note that
+ # Gitlab::Database.check_postgres_version_and_print_warning is called
+ # at startup, but that generates its own
+ # `Gitlab::Database::Reflection` so the result is not memoized by
+ # callers of `ApplicationRecord.database.version`, such as
+ # `Gitlab::Database::AsWithMaterialized.materialized_supported?`.
+ # TODO This can be removed once https://gitlab.com/gitlab-org/gitlab/-/issues/325639 is completed.
+ [ApplicationRecord, ::Ci::ApplicationRecord].each { |record| record.database.version }
+
+ config.around(:each, :reestablished_active_record_base) do |example|
+ with_reestablished_active_record_base(reconnect: example.metadata.fetch(:reconnect, true)) do
+ example.run
+ end
+ end
+
+ config.around(:each, :add_ci_connection) do |example|
+ with_added_ci_connection do
+ example.run
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb
index 1fa8ccf4b55..be7e19492d7 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -161,9 +161,10 @@ RSpec.shared_examples 'wiki model' do
let(:wiki_pages) { subject.list_pages }
before do
- subject.create_page('index', 'This is an index')
+ # The order is intentional
subject.create_page('index2', 'This is an index2')
- subject.create_page('an index3', 'This is an index3')
+ subject.create_page('index', 'This is an index')
+ subject.create_page('index3', 'This is an index3')
end
it 'returns an array of WikiPage instances' do
@@ -183,13 +184,47 @@ RSpec.shared_examples 'wiki model' do
context 'with limit option' do
it 'returns limited set of pages' do
- expect(subject.list_pages(limit: 1).count).to eq(1)
+ expect(
+ subject.list_pages(limit: 1).map(&:title)
+ ).to eql(%w[index])
+ end
+
+ it 'returns all set of pages if limit is more than the total pages' do
+ expect(subject.list_pages(limit: 4).count).to eq(3)
+ end
+
+ it 'returns all set of pages if limit is 0' do
+ expect(subject.list_pages(limit: 0).count).to eq(3)
+ end
+ end
+
+ context 'with offset option' do
+ it 'returns offset-ed set of pages' do
+ expect(
+ subject.list_pages(offset: 1).map(&:title)
+ ).to eq(%w[index2 index3])
+
+ expect(
+ subject.list_pages(offset: 2).map(&:title)
+ ).to eq(["index3"])
+ expect(subject.list_pages(offset: 3).count).to eq(0)
+ expect(subject.list_pages(offset: 4).count).to eq(0)
+ end
+
+ it 'returns all set of pages if offset is 0' do
+ expect(subject.list_pages(offset: 0).count).to eq(3)
+ end
+
+ it 'can combines with limit' do
+ expect(
+ subject.list_pages(offset: 1, limit: 1).map(&:title)
+ ).to eq(["index2"])
end
end
context 'with sorting options' do
it 'returns pages sorted by title by default' do
- pages = ['an index3', 'index', 'index2']
+ pages = %w[index index2 index3]
expect(subject.list_pages.map(&:title)).to eq(pages)
expect(subject.list_pages(direction: 'desc').map(&:title)).to eq(pages.reverse)
@@ -200,9 +235,9 @@ RSpec.shared_examples 'wiki model' do
let(:pages) { subject.list_pages(load_content: true) }
it 'loads WikiPage content' do
- expect(pages.first.content).to eq('This is an index3')
- expect(pages.second.content).to eq('This is an index')
- expect(pages.third.content).to eq('This is an index2')
+ expect(pages.first.content).to eq('This is an index')
+ expect(pages.second.content).to eq('This is an index2')
+ expect(pages.third.content).to eq('This is an index3')
end
end
end
diff --git a/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb b/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb
new file mode 100644
index 00000000000..ac17915c15a
--- /dev/null
+++ b/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples "setting work item's milestone" do
+ context "when 'milestone' param does not exist" do
+ let(:params) { {} }
+
+ it "does not set the work item's milestone" do
+ expect { execute_callback }.to not_change(work_item, :milestone)
+ end
+ end
+
+ context "when 'milestone' is not in the work item's project's hierarchy" do
+ let(:another_group_milestone) { create(:milestone, group: create(:group)) }
+ let(:params) { { milestone_id: another_group_milestone.id } }
+
+ it "does not set the work item's milestone" do
+ expect { execute_callback }.to not_change(work_item, :milestone)
+ end
+ end
+
+ context 'when assigning a group milestone' do
+ let(:params) { { milestone_id: group_milestone.id } }
+
+ it "sets the work item's milestone" do
+ expect { execute_callback }
+ .to change(work_item, :milestone)
+ .from(nil)
+ .to(group_milestone)
+ end
+ end
+
+ context 'when assigning a project milestone' do
+ let(:params) { { milestone_id: project_milestone.id } }
+
+ it "sets the work item's milestone" do
+ expect { execute_callback }
+ .to change(work_item, :milestone)
+ .from(nil)
+ .to(project_milestone)
+ end
+ end
+end
diff --git a/spec/support_specs/database/multiple_databases_spec.rb b/spec/support_specs/database/multiple_databases_helpers_spec.rb
index 0b019462077..eb0e980d376 100644
--- a/spec/support_specs/database/multiple_databases_spec.rb
+++ b/spec/support_specs/database/multiple_databases_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'Database::MultipleDatabases' do
+RSpec.describe 'Database::MultipleDatabasesHelpers' do
let(:query) do
<<~SQL
WITH cte AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (SELECT 1) SELECT 1;
@@ -72,7 +72,8 @@ RSpec.describe 'Database::MultipleDatabases' do
context 'when reconnect is false' do
it 'does raise exception' do
with_reestablished_active_record_base(reconnect: false) do
- expect { ApplicationRecord.connection.execute("SELECT 1") }.to raise_error(ActiveRecord::ConnectionNotEstablished)
+ expect { ApplicationRecord.connection.execute("SELECT 1") }
+ .to raise_error(ActiveRecord::ConnectionNotEstablished)
end
end
end