Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
51ba1dfa3b
commit
f825fd1d88
2
Gemfile
2
Gemfile
|
@ -503,7 +503,7 @@ gem 'ssh_data', '~> 1.3'
|
||||||
gem 'spamcheck', '~> 1.0.0'
|
gem 'spamcheck', '~> 1.0.0'
|
||||||
|
|
||||||
# Gitaly GRPC protocol definitions
|
# Gitaly GRPC protocol definitions
|
||||||
gem 'gitaly', '~> 15.4.0-rc2'
|
gem 'gitaly', '~> 15.5.0'
|
||||||
|
|
||||||
# KAS GRPC protocol definitions
|
# KAS GRPC protocol definitions
|
||||||
gem 'kas-grpc', '~> 0.0.2'
|
gem 'kas-grpc', '~> 0.0.2'
|
||||||
|
|
|
@ -198,7 +198,7 @@
|
||||||
{"name":"gettext_i18n_rails","version":"1.8.0","platform":"ruby","checksum":"95e5cf8440b1e08705b27f2bccb56143272c5a7a0dabcf54ea1bd701140a496f"},
|
{"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":"gettext_i18n_rails_js","version":"1.3.0","platform":"ruby","checksum":"5d10afe4be3639bff78c50a56768c20f39aecdabc580c08aa45573911c2bd687"},
|
||||||
{"name":"git","version":"1.11.0","platform":"ruby","checksum":"7e95ba4da8298a0373ef1a6862aa22007d761f3c8274b675aa787966fecea0f1"},
|
{"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":"github-markup","version":"1.7.0","platform":"ruby","checksum":"97eb27c70662d9cc1d5997cd6c99832026fae5d4913b5dce1ce6c9f65078e69d"},
|
||||||
{"name":"gitlab","version":"4.16.1","platform":"ruby","checksum":"13fd7059cbdad5a1a21b15fa2cf9070b97d92e27f8c688581fe3d84dc038074f"},
|
{"name":"gitlab","version":"4.16.1","platform":"ruby","checksum":"13fd7059cbdad5a1a21b15fa2cf9070b97d92e27f8c688581fe3d84dc038074f"},
|
||||||
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
|
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
|
||||||
|
|
|
@ -547,7 +547,7 @@ GEM
|
||||||
rails (>= 3.2.0)
|
rails (>= 3.2.0)
|
||||||
git (1.11.0)
|
git (1.11.0)
|
||||||
rchardet (~> 1.8)
|
rchardet (~> 1.8)
|
||||||
gitaly (15.4.0.pre.rc2)
|
gitaly (15.5.0)
|
||||||
grpc (~> 1.0)
|
grpc (~> 1.0)
|
||||||
github-markup (1.7.0)
|
github-markup (1.7.0)
|
||||||
gitlab (4.16.1)
|
gitlab (4.16.1)
|
||||||
|
@ -1626,7 +1626,7 @@ DEPENDENCIES
|
||||||
gettext (~> 3.3)
|
gettext (~> 3.3)
|
||||||
gettext_i18n_rails (~> 1.8.0)
|
gettext_i18n_rails (~> 1.8.0)
|
||||||
gettext_i18n_rails_js (~> 1.3)
|
gettext_i18n_rails_js (~> 1.3)
|
||||||
gitaly (~> 15.4.0.pre.rc2)
|
gitaly (~> 15.5.0)
|
||||||
github-markup (~> 1.7.0)
|
github-markup (~> 1.7.0)
|
||||||
gitlab-chronic (~> 0.10.5)
|
gitlab-chronic (~> 0.10.5)
|
||||||
gitlab-dangerfiles (~> 3.6.1)
|
gitlab-dangerfiles (~> 3.6.1)
|
||||||
|
|
|
@ -143,8 +143,9 @@
|
||||||
"WorkItemWidgetHierarchy",
|
"WorkItemWidgetHierarchy",
|
||||||
"WorkItemWidgetIteration",
|
"WorkItemWidgetIteration",
|
||||||
"WorkItemWidgetLabels",
|
"WorkItemWidgetLabels",
|
||||||
|
"WorkItemWidgetMilestone",
|
||||||
"WorkItemWidgetStartAndDueDate",
|
"WorkItemWidgetStartAndDueDate",
|
||||||
"WorkItemWidgetStatus",
|
"WorkItemWidgetStatus",
|
||||||
"WorkItemWidgetWeight"
|
"WorkItemWidgetWeight"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -44,6 +44,7 @@ export default {
|
||||||
'signupEnabled',
|
'signupEnabled',
|
||||||
'requireAdminApprovalAfterUserSignup',
|
'requireAdminApprovalAfterUserSignup',
|
||||||
'sendUserConfirmationEmail',
|
'sendUserConfirmationEmail',
|
||||||
|
'emailConfirmationSetting',
|
||||||
'minimumPasswordLength',
|
'minimumPasswordLength',
|
||||||
'minimumPasswordLengthMin',
|
'minimumPasswordLengthMin',
|
||||||
'minimumPasswordLengthMax',
|
'minimumPasswordLengthMax',
|
||||||
|
@ -66,6 +67,7 @@ export default {
|
||||||
signupEnabled: this.signupEnabled,
|
signupEnabled: this.signupEnabled,
|
||||||
requireAdminApproval: this.requireAdminApprovalAfterUserSignup,
|
requireAdminApproval: this.requireAdminApprovalAfterUserSignup,
|
||||||
sendConfirmationEmail: this.sendUserConfirmationEmail,
|
sendConfirmationEmail: this.sendUserConfirmationEmail,
|
||||||
|
emailConfirmationSetting: this.emailConfirmationSetting,
|
||||||
minimumPasswordLength: this.minimumPasswordLength,
|
minimumPasswordLength: this.minimumPasswordLength,
|
||||||
minimumPasswordLengthMin: this.minimumPasswordLengthMin,
|
minimumPasswordLengthMin: this.minimumPasswordLengthMin,
|
||||||
minimumPasswordLengthMax: this.minimumPasswordLengthMax,
|
minimumPasswordLengthMax: this.minimumPasswordLengthMax,
|
||||||
|
@ -199,6 +201,15 @@ export default {
|
||||||
signupEnabledLabel: s__('ApplicationSettings|Sign-up enabled'),
|
signupEnabledLabel: s__('ApplicationSettings|Sign-up enabled'),
|
||||||
requireAdminApprovalLabel: s__('ApplicationSettings|Require admin approval for new sign-ups'),
|
requireAdminApprovalLabel: s__('ApplicationSettings|Require admin approval for new sign-ups'),
|
||||||
sendConfirmationEmailLabel: s__('ApplicationSettings|Send confirmation email on sign-up'),
|
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__(
|
minimumPasswordLengthLabel: s__(
|
||||||
'ApplicationSettings|Minimum password length (number of characters)',
|
'ApplicationSettings|Minimum password length (number of characters)',
|
||||||
),
|
),
|
||||||
|
@ -276,6 +287,24 @@ export default {
|
||||||
:label="$options.i18n.sendConfirmationEmailLabel"
|
: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
|
<gl-form-group
|
||||||
:label="$options.i18n.userCapLabel"
|
:label="$options.i18n.userCapLabel"
|
||||||
:description="$options.i18n.userCapDescription"
|
:description="$options.i18n.userCapDescription"
|
||||||
|
|
|
@ -440,7 +440,7 @@ class ProjectsController < Projects::ApplicationController
|
||||||
def operations_feature_attributes
|
def operations_feature_attributes
|
||||||
if Feature.enabled?(:split_operations_visibility_permissions, project)
|
if Feature.enabled?(:split_operations_visibility_permissions, project)
|
||||||
%i[
|
%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
|
else
|
||||||
%i[operations_access_level]
|
%i[operations_access_level]
|
||||||
|
|
|
@ -33,6 +33,9 @@ module Mutations
|
||||||
argument :labels_widget, ::Types::WorkItems::Widgets::LabelsUpdateInputType,
|
argument :labels_widget, ::Types::WorkItems::Widgets::LabelsUpdateInputType,
|
||||||
required: false,
|
required: false,
|
||||||
description: 'Input for labels widget.'
|
description: 'Input for labels widget.'
|
||||||
|
argument :milestone_widget, ::Types::WorkItems::Widgets::MilestoneInputType,
|
||||||
|
required: false,
|
||||||
|
description: 'Input for milestone widget.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,9 @@ module Mutations
|
||||||
argument :hierarchy_widget, ::Types::WorkItems::Widgets::HierarchyCreateInputType,
|
argument :hierarchy_widget, ::Types::WorkItems::Widgets::HierarchyCreateInputType,
|
||||||
required: false,
|
required: false,
|
||||||
description: 'Input for hierarchy widget.'
|
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,
|
argument :project_path, GraphQL::Types::ID,
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Full path of the project the work item is associated with.'
|
description: 'Full path of the project the work item is associated with.'
|
||||||
|
|
|
@ -55,7 +55,8 @@ module Resolvers
|
||||||
last_edited_by: :last_edited_by,
|
last_edited_by: :last_edited_by,
|
||||||
assignees: :assignees,
|
assignees: :assignees,
|
||||||
parent: :work_item_parent,
|
parent: :work_item_parent,
|
||||||
labels: :labels
|
labels: :labels,
|
||||||
|
milestone: :milestone
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,8 @@ module Types
|
||||||
::Types::WorkItems::Widgets::HierarchyType,
|
::Types::WorkItems::Widgets::HierarchyType,
|
||||||
::Types::WorkItems::Widgets::LabelsType,
|
::Types::WorkItems::Widgets::LabelsType,
|
||||||
::Types::WorkItems::Widgets::AssigneesType,
|
::Types::WorkItems::Widgets::AssigneesType,
|
||||||
::Types::WorkItems::Widgets::StartAndDueDateType
|
::Types::WorkItems::Widgets::StartAndDueDateType,
|
||||||
|
::Types::WorkItems::Widgets::MilestoneType
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
def self.ce_orphan_types
|
def self.ce_orphan_types
|
||||||
|
@ -38,6 +39,8 @@ module Types
|
||||||
::Types::WorkItems::Widgets::LabelsType
|
::Types::WorkItems::Widgets::LabelsType
|
||||||
when ::WorkItems::Widgets::StartAndDueDate
|
when ::WorkItems::Widgets::StartAndDueDate
|
||||||
::Types::WorkItems::Widgets::StartAndDueDateType
|
::Types::WorkItems::Widgets::StartAndDueDateType
|
||||||
|
when ::WorkItems::Widgets::Milestone
|
||||||
|
::Types::WorkItems::Widgets::MilestoneType
|
||||||
else
|
else
|
||||||
raise "Unknown GraphQL type for widget #{object}"
|
raise "Unknown GraphQL type for widget #{object}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -241,6 +241,7 @@ module ApplicationSettingsHelper
|
||||||
:eks_access_key_id,
|
:eks_access_key_id,
|
||||||
:eks_secret_access_key,
|
:eks_secret_access_key,
|
||||||
:email_author_in_body,
|
:email_author_in_body,
|
||||||
|
:email_confirmation_setting,
|
||||||
:enabled_git_access_protocol,
|
:enabled_git_access_protocol,
|
||||||
:enforce_terms,
|
:enforce_terms,
|
||||||
:error_tracking_enabled,
|
:error_tracking_enabled,
|
||||||
|
@ -544,6 +545,7 @@ module ApplicationSettingsHelper
|
||||||
signup_enabled: @application_setting[:signup_enabled].to_s,
|
signup_enabled: @application_setting[:signup_enabled].to_s,
|
||||||
require_admin_approval_after_user_signup: @application_setting[:require_admin_approval_after_user_signup].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,
|
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: @application_setting[:minimum_password_length],
|
||||||
minimum_password_length_min: ApplicationSetting::DEFAULT_MINIMUM_PASSWORD_LENGTH,
|
minimum_password_length_min: ApplicationSetting::DEFAULT_MINIMUM_PASSWORD_LENGTH,
|
||||||
minimum_password_length_max: Devise.password_length.max,
|
minimum_password_length_max: Devise.password_length.max,
|
||||||
|
|
|
@ -20,6 +20,7 @@ class ApplicationSetting < ApplicationRecord
|
||||||
'Admin Area > Settings > General > Kroki'
|
'Admin Area > Settings > General > Kroki'
|
||||||
|
|
||||||
enum whats_new_variant: { all_tiers: 0, current_tier: 1, disabled: 2 }, _prefix: true
|
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 :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption) ? :optional : :required }
|
||||||
add_authentication_token_field :health_check_access_token
|
add_authentication_token_field :health_check_access_token
|
||||||
|
|
|
@ -110,6 +110,10 @@ module ProjectFeaturesCompatibility
|
||||||
write_feature_attribute_string(:releases_access_level, value)
|
write_feature_attribute_string(:releases_access_level, value)
|
||||||
end
|
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
|
# 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
|
# container_registry_enabled attribute. They can instead set the container_registry_access_level
|
||||||
# attribute.
|
# attribute.
|
||||||
|
|
|
@ -4,11 +4,6 @@ class NamespaceSetting < ApplicationRecord
|
||||||
include CascadingNamespaceSettingAttribute
|
include CascadingNamespaceSettingAttribute
|
||||||
include Sanitizable
|
include Sanitizable
|
||||||
include ChronicDurationAttribute
|
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
|
cascading_attr :delayed_project_removal
|
||||||
|
|
||||||
|
|
|
@ -451,7 +451,7 @@ class Project < ApplicationRecord
|
||||||
:metrics_dashboard_access_level, :analytics_access_level,
|
:metrics_dashboard_access_level, :analytics_access_level,
|
||||||
:operations_access_level, :security_and_compliance_access_level,
|
:operations_access_level, :security_and_compliance_access_level,
|
||||||
:container_registry_access_level, :environments_access_level, :feature_flags_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
|
to: :project_feature, allow_nil: true
|
||||||
|
|
||||||
delegate :show_default_award_emojis, :show_default_award_emojis=,
|
delegate :show_default_award_emojis, :show_default_award_emojis=,
|
||||||
|
|
|
@ -25,6 +25,7 @@ class ProjectFeature < ApplicationRecord
|
||||||
environments
|
environments
|
||||||
feature_flags
|
feature_flags
|
||||||
releases
|
releases
|
||||||
|
infrastructure
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
EXPORTABLE_FEATURES = (FEATURES - [:security_and_compliance, :pages]).freeze
|
EXPORTABLE_FEATURES = (FEATURES - [:security_and_compliance, :pages]).freeze
|
||||||
|
|
|
@ -190,7 +190,7 @@ class Wiki
|
||||||
end
|
end
|
||||||
|
|
||||||
def empty?
|
def empty?
|
||||||
!repository_exists? || list_page_paths.empty?
|
!repository_exists? || list_page_paths(limit: 1).empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
def exists?
|
def exists?
|
||||||
|
@ -207,9 +207,9 @@ class Wiki
|
||||||
#
|
#
|
||||||
# Returns an Array of GitLab WikiPage instances or an
|
# Returns an Array of GitLab WikiPage instances or an
|
||||||
# empty Array if this Wiki has no pages.
|
# 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?
|
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
|
end
|
||||||
|
|
||||||
def sidebar_entries(limit: Gitlab::WikiPages::MAX_SIDEBAR_PAGES, **options)
|
def sidebar_entries(limit: Gitlab::WikiPages::MAX_SIDEBAR_PAGES, **options)
|
||||||
|
@ -457,7 +457,7 @@ class Wiki
|
||||||
escaped_path = RE2::Regexp.escape(sluggified_title(title))
|
escaped_path = RE2::Regexp.escape(sluggified_title(title))
|
||||||
path_regexp = Gitlab::EncodingHelper.encode_utf8_no_detect("(?i)^#{escaped_path}\\.(#{file_extension_regexp})$")
|
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?
|
return if matched_files.blank?
|
||||||
|
|
||||||
Gitlab::EncodingHelper.encode_utf8_no_detect(matched_files.first)
|
Gitlab::EncodingHelper.encode_utf8_no_detect(matched_files.first)
|
||||||
|
@ -509,15 +509,15 @@ class Wiki
|
||||||
path.sub(/\.[^.]+\z/, "")
|
path.sub(/\.[^.]+\z/, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_page_paths
|
def list_page_paths(limit: 0, offset: 0)
|
||||||
return [] if repository.empty?
|
return [] if repository.empty?
|
||||||
|
|
||||||
path_regexp = Gitlab::EncodingHelper.encode_utf8_no_detect("(?i)\\.(#{file_extension_regexp})$")
|
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
|
end
|
||||||
|
|
||||||
def list_pages_with_repository_rpcs(limit:, direction:, load_content:)
|
def list_pages_with_repository_rpcs(direction:, load_content:, limit:, offset:)
|
||||||
paths = list_page_paths
|
paths = list_page_paths(limit: limit, offset: offset)
|
||||||
return [] if paths.empty?
|
return [] if paths.empty?
|
||||||
|
|
||||||
pages = paths.map do |path|
|
pages = paths.map do |path|
|
||||||
|
|
|
@ -21,11 +21,13 @@ module WorkItems
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
WIDGETS_FOR_TYPE = {
|
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],
|
incident: [Widgets::Description, Widgets::Hierarchy],
|
||||||
test_case: [Widgets::Description],
|
test_case: [Widgets::Description],
|
||||||
requirement: [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
|
}.freeze
|
||||||
|
|
||||||
WI_TYPES_WITH_CREATED_HEADER = %w[issue incident].freeze
|
WI_TYPES_WITH_CREATED_HEADER = %w[issue incident].freeze
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WorkItems
|
||||||
|
module Widgets
|
||||||
|
class Milestone < Base
|
||||||
|
delegate :milestone, to: :work_item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -213,6 +213,7 @@ class ProjectPolicy < BasePolicy
|
||||||
environments
|
environments
|
||||||
feature_flags
|
feature_flags
|
||||||
releases
|
releases
|
||||||
|
infrastructure
|
||||||
]
|
]
|
||||||
|
|
||||||
features.each do |f|
|
features.each do |f|
|
||||||
|
@ -409,6 +410,14 @@ class ProjectPolicy < BasePolicy
|
||||||
prevent(*create_read_update_admin_destroy(:alert_management_alert))
|
prevent(*create_read_update_admin_destroy(:alert_management_alert))
|
||||||
end
|
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
|
rule { can?(:metrics_dashboard) }.policy do
|
||||||
enable :read_prometheus
|
enable :read_prometheus
|
||||||
enable :read_deployment
|
enable :read_deployment
|
||||||
|
|
|
@ -30,6 +30,13 @@ module WorkItems
|
||||||
error(e.message, :unprocessable_entity)
|
error(e.message, :unprocessable_entity)
|
||||||
end
|
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)
|
def transaction_create(work_item)
|
||||||
super.tap do |save_result|
|
super.tap do |save_result|
|
||||||
if save_result
|
if save_result
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
001b43cc0006b8f936310171ff2d12993eece1378f64945e6835728f540815ba
|
|
@ -11493,6 +11493,7 @@ CREATE TABLE application_settings (
|
||||||
password_expires_in_days integer DEFAULT 90 NOT NULL,
|
password_expires_in_days integer DEFAULT 90 NOT NULL,
|
||||||
password_expires_notice_before_days integer DEFAULT 7 NOT NULL,
|
password_expires_notice_before_days integer DEFAULT 7 NOT NULL,
|
||||||
product_analytics_enabled boolean DEFAULT false 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_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_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)),
|
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
||||||
|
|
|
@ -5774,6 +5774,7 @@ Input type: `WorkItemCreateInput`
|
||||||
| <a id="mutationworkitemcreateconfidential"></a>`confidential` | [`Boolean`](#boolean) | Sets the work item confidentiality. |
|
| <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="mutationworkitemcreatedescription"></a>`description` | [`String`](#string) | Description of the work item. |
|
||||||
| <a id="mutationworkitemcreatehierarchywidget"></a>`hierarchyWidget` | [`WorkItemWidgetHierarchyCreateInput`](#workitemwidgethierarchycreateinput) | Input for hierarchy widget. |
|
| <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="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="mutationworkitemcreatetitle"></a>`title` | [`String!`](#string) | Title of the work item. |
|
||||||
| <a id="mutationworkitemcreateworkitemtypeid"></a>`workItemTypeId` | [`WorkItemsTypeID!`](#workitemstypeid) | Global ID of a work item type. |
|
| <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="mutationworkitemupdateid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
|
||||||
| <a id="mutationworkitemupdateiterationwidget"></a>`iterationWidget` | [`WorkItemWidgetIterationInput`](#workitemwidgetiterationinput) | Input for iteration widget. |
|
| <a id="mutationworkitemupdateiterationwidget"></a>`iterationWidget` | [`WorkItemWidgetIterationInput`](#workitemwidgetiterationinput) | Input for iteration widget. |
|
||||||
| <a id="mutationworkitemupdatelabelswidget"></a>`labelsWidget` | [`WorkItemWidgetLabelsUpdateInput`](#workitemwidgetlabelsupdateinput) | Input for labels 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="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="mutationworkitemupdatestateevent"></a>`stateEvent` | [`WorkItemStateEvent`](#workitemstateevent) | Close or reopen a work item. |
|
||||||
| <a id="mutationworkitemupdatestatuswidget"></a>`statusWidget` | [`StatusInput`](#statusinput) | Input for status widget. |
|
| <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="workitemwidgetlabelslabels"></a>`labels` | [`LabelConnection`](#labelconnection) | Labels assigned to the work item. (see [Connections](#connections)) |
|
||||||
| <a id="workitemwidgetlabelstype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
|
| <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`
|
### `WorkItemWidgetStartAndDueDate`
|
||||||
|
|
||||||
Represents a start and due date widget.
|
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="workitemwidgettypehierarchy"></a>`HIERARCHY` | Hierarchy widget. |
|
||||||
| <a id="workitemwidgettypeiteration"></a>`ITERATION` | Iteration widget. |
|
| <a id="workitemwidgettypeiteration"></a>`ITERATION` | Iteration widget. |
|
||||||
| <a id="workitemwidgettypelabels"></a>`LABELS` | Labels 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="workitemwidgettypestart_and_due_date"></a>`START_AND_DUE_DATE` | Start And Due Date widget. |
|
||||||
| <a id="workitemwidgettypestatus"></a>`STATUS` | Status widget. |
|
| <a id="workitemwidgettypestatus"></a>`STATUS` | Status widget. |
|
||||||
| <a id="workitemwidgettypeweight"></a>`WEIGHT` | Weight widget. |
|
| <a id="workitemwidgettypeweight"></a>`WEIGHT` | Weight widget. |
|
||||||
|
@ -23314,6 +23328,7 @@ Implementations:
|
||||||
- [`WorkItemWidgetHierarchy`](#workitemwidgethierarchy)
|
- [`WorkItemWidgetHierarchy`](#workitemwidgethierarchy)
|
||||||
- [`WorkItemWidgetIteration`](#workitemwidgetiteration)
|
- [`WorkItemWidgetIteration`](#workitemwidgetiteration)
|
||||||
- [`WorkItemWidgetLabels`](#workitemwidgetlabels)
|
- [`WorkItemWidgetLabels`](#workitemwidgetlabels)
|
||||||
|
- [`WorkItemWidgetMilestone`](#workitemwidgetmilestone)
|
||||||
- [`WorkItemWidgetStartAndDueDate`](#workitemwidgetstartandduedate)
|
- [`WorkItemWidgetStartAndDueDate`](#workitemwidgetstartandduedate)
|
||||||
- [`WorkItemWidgetStatus`](#workitemwidgetstatus)
|
- [`WorkItemWidgetStatus`](#workitemwidgetstatus)
|
||||||
- [`WorkItemWidgetWeight`](#workitemwidgetweight)
|
- [`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="workitemupdatedtaskinputhierarchywidget"></a>`hierarchyWidget` | [`WorkItemWidgetHierarchyUpdateInput`](#workitemwidgethierarchyupdateinput) | Input for hierarchy widget. |
|
||||||
| <a id="workitemupdatedtaskinputid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
|
| <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="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="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="workitemupdatedtaskinputstateevent"></a>`stateEvent` | [`WorkItemStateEvent`](#workitemstateevent) | Close or reopen a work item. |
|
||||||
| <a id="workitemupdatedtaskinputtitle"></a>`title` | [`String`](#string) | Title of the 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="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. |
|
| <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`
|
### `WorkItemWidgetStartAndDueDateUpdateInput`
|
||||||
|
|
||||||
#### Arguments
|
#### Arguments
|
||||||
|
|
|
@ -1054,19 +1054,19 @@ module Gitlab
|
||||||
end
|
end
|
||||||
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{^/*}, "")
|
safe_query = query.sub(%r{^/*}, "")
|
||||||
ref ||= root_ref
|
ref ||= root_ref
|
||||||
|
|
||||||
return [] if empty? || safe_query.blank?
|
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)
|
Gitlab::EncodingHelper.encode_utf8(file)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def search_files_by_regexp(filter, ref = 'HEAD')
|
def search_files_by_regexp(filter, ref = 'HEAD', limit: 0, offset: 0)
|
||||||
gitaly_repository_client.search_files_by_regexp(ref, filter).map do |file|
|
gitaly_repository_client.search_files_by_regexp(ref, filter, limit: limit, offset: offset).map do |file|
|
||||||
Gitlab::EncodingHelper.encode_utf8(file)
|
Gitlab::EncodingHelper.encode_utf8(file)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -303,8 +303,8 @@ module Gitlab
|
||||||
GitalyClient.call(@storage, :repository_service, :get_raw_changes, request, timeout: GitalyClient.fast_timeout)
|
GitalyClient.call(@storage, :repository_service, :get_raw_changes, request, timeout: GitalyClient.fast_timeout)
|
||||||
end
|
end
|
||||||
|
|
||||||
def search_files_by_name(ref, query)
|
def search_files_by_name(ref, query, limit: 0, offset: 0)
|
||||||
request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: query)
|
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)
|
GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -314,8 +314,8 @@ module Gitlab
|
||||||
search_results_from_response(response, options)
|
search_results_from_response(response, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def search_files_by_regexp(ref, filter)
|
def search_files_by_regexp(ref, filter, limit: 0, offset: 0)
|
||||||
request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: '.', filter: filter)
|
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)
|
GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -302,6 +302,7 @@ included_attributes:
|
||||||
- :environments_access_level
|
- :environments_access_level
|
||||||
- :feature_flags_access_level
|
- :feature_flags_access_level
|
||||||
- :releases_access_level
|
- :releases_access_level
|
||||||
|
- :infrastructure_access_level
|
||||||
prometheus_metrics:
|
prometheus_metrics:
|
||||||
- :created_at
|
- :created_at
|
||||||
- :updated_at
|
- :updated_at
|
||||||
|
@ -717,6 +718,7 @@ included_attributes:
|
||||||
- :environments_access_level
|
- :environments_access_level
|
||||||
- :feature_flags_access_level
|
- :feature_flags_access_level
|
||||||
- :releases_access_level
|
- :releases_access_level
|
||||||
|
- :infrastructure_access_level
|
||||||
- :allow_merge_on_skipped_pipeline
|
- :allow_merge_on_skipped_pipeline
|
||||||
- :auto_devops_deploy_strategy
|
- :auto_devops_deploy_strategy
|
||||||
- :auto_devops_enabled
|
- :auto_devops_enabled
|
||||||
|
|
|
@ -20,6 +20,10 @@ module Sidebars
|
||||||
# Push Rules are the only group setting that can also be edited by maintainers.
|
# 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).
|
# Create an empty sub-menu here and EE adds Repository menu item (with only Push Rules).
|
||||||
return true
|
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
|
end
|
||||||
|
|
||||||
false
|
false
|
||||||
|
|
|
@ -6,7 +6,7 @@ module Sidebars
|
||||||
class InfrastructureMenu < ::Sidebars::Menu
|
class InfrastructureMenu < ::Sidebars::Menu
|
||||||
override :configure_menu_items
|
override :configure_menu_items
|
||||||
def 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(kubernetes_menu_item)
|
||||||
add_item(terraform_menu_item)
|
add_item(terraform_menu_item)
|
||||||
|
@ -34,6 +34,14 @@ module Sidebars
|
||||||
|
|
||||||
private
|
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
|
def kubernetes_menu_item
|
||||||
unless can?(context.current_user, :read_cluster, context.project)
|
unless can?(context.current_user, :read_cluster, context.project)
|
||||||
return ::Sidebars::NilMenuItem.new(item_id: :kubernetes)
|
return ::Sidebars::NilMenuItem.new(item_id: :kubernetes)
|
||||||
|
|
|
@ -4717,6 +4717,9 @@ msgstr ""
|
||||||
msgid "ApplicationSettings|Domain denylist"
|
msgid "ApplicationSettings|Domain denylist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ApplicationSettings|Email confirmation settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "ApplicationSettings|Email restrictions"
|
msgid "ApplicationSettings|Email restrictions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -4735,9 +4738,18 @@ msgstr ""
|
||||||
msgid "ApplicationSettings|Enter denylist manually"
|
msgid "ApplicationSettings|Enter denylist manually"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ApplicationSettings|Hard"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "ApplicationSettings|Minimum password length (number of characters)"
|
msgid "ApplicationSettings|Minimum password length (number of characters)"
|
||||||
msgstr ""
|
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"
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -4765,6 +4777,9 @@ msgstr ""
|
||||||
msgid "ApplicationSettings|See %{linkStart}password policy guidelines%{linkEnd}."
|
msgid "ApplicationSettings|See %{linkStart}password policy guidelines%{linkEnd}."
|
||||||
msgstr ""
|
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"
|
msgid "ApplicationSettings|Send confirmation email on sign-up"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
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 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile
|
||||||
gem 'allure-rspec', '~> 2.18.0'
|
gem 'allure-rspec', '~> 2.18.0'
|
||||||
gem 'capybara', '~> 3.37.1'
|
gem 'capybara', '~> 3.37.1'
|
||||||
|
|
|
@ -100,7 +100,7 @@ GEM
|
||||||
gitlab (4.18.0)
|
gitlab (4.18.0)
|
||||||
httparty (~> 0.18)
|
httparty (~> 0.18)
|
||||||
terminal-table (>= 1.5.1)
|
terminal-table (>= 1.5.1)
|
||||||
gitlab-qa (8.8.0)
|
gitlab-qa (8.9.0)
|
||||||
activesupport (~> 6.1)
|
activesupport (~> 6.1)
|
||||||
gitlab (~> 4.18.0)
|
gitlab (~> 4.18.0)
|
||||||
http (~> 5.0)
|
http (~> 5.0)
|
||||||
|
@ -314,7 +314,7 @@ DEPENDENCIES
|
||||||
faraday-retry (~> 2.0)
|
faraday-retry (~> 2.0)
|
||||||
fog-core (= 2.1.0)
|
fog-core (= 2.1.0)
|
||||||
fog-google (~> 1.19)
|
fog-google (~> 1.19)
|
||||||
gitlab-qa (~> 8, >= 8.8.0)
|
gitlab-qa (~> 8, >= 8.9.0)
|
||||||
influxdb-client (~> 2.7)
|
influxdb-client (~> 2.7)
|
||||||
knapsack (~> 4.0)
|
knapsack (~> 4.0)
|
||||||
nokogiri (~> 1.13, >= 1.13.9)
|
nokogiri (~> 1.13, >= 1.13.9)
|
||||||
|
|
|
@ -27,6 +27,7 @@ module RuboCop
|
||||||
environments
|
environments
|
||||||
feature_flags
|
feature_flags
|
||||||
releases
|
releases
|
||||||
|
infrastructure
|
||||||
].freeze
|
].freeze
|
||||||
EE_FEATURES = %i[requirements].freeze
|
EE_FEATURES = %i[requirements].freeze
|
||||||
ALL_FEATURES = (FEATURES + EE_FEATURES).freeze
|
ALL_FEATURES = (FEATURES + EE_FEATURES).freeze
|
||||||
|
|
|
@ -921,6 +921,7 @@ RSpec.describe ProjectsController do
|
||||||
feature_flags_access_level
|
feature_flags_access_level
|
||||||
releases_access_level
|
releases_access_level
|
||||||
monitor_access_level
|
monitor_access_level
|
||||||
|
infrastructure_access_level
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ FactoryBot.define do
|
||||||
environments_access_level { ProjectFeature::ENABLED }
|
environments_access_level { ProjectFeature::ENABLED }
|
||||||
feature_flags_access_level { ProjectFeature::ENABLED }
|
feature_flags_access_level { ProjectFeature::ENABLED }
|
||||||
releases_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
|
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
|
||||||
# `#ci_cd_settings` relation needs to be created first
|
# `#ci_cd_settings` relation needs to be created first
|
||||||
|
|
|
@ -205,6 +205,22 @@ RSpec.describe 'Admin updates settings' do
|
||||||
expect(page).to have_content "Application settings saved successfully"
|
expect(page).to have_content "Application settings saved successfully"
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
it 'change Sign-in restrictions' do
|
it 'change Sign-in restrictions' do
|
||||||
|
|
|
@ -4,6 +4,7 @@ export const rawMockData = {
|
||||||
signupEnabled: 'true',
|
signupEnabled: 'true',
|
||||||
requireAdminApprovalAfterUserSignup: 'true',
|
requireAdminApprovalAfterUserSignup: 'true',
|
||||||
sendUserConfirmationEmail: 'true',
|
sendUserConfirmationEmail: 'true',
|
||||||
|
emailConfirmationSetting: 'hard',
|
||||||
minimumPasswordLength: '8',
|
minimumPasswordLength: '8',
|
||||||
minimumPasswordLengthMin: '3',
|
minimumPasswordLengthMin: '3',
|
||||||
minimumPasswordLengthMax: '10',
|
minimumPasswordLengthMax: '10',
|
||||||
|
@ -30,6 +31,7 @@ export const mockData = {
|
||||||
signupEnabled: true,
|
signupEnabled: true,
|
||||||
requireAdminApprovalAfterUserSignup: true,
|
requireAdminApprovalAfterUserSignup: true,
|
||||||
sendUserConfirmationEmail: true,
|
sendUserConfirmationEmail: true,
|
||||||
|
emailConfirmationSetting: 'hard',
|
||||||
minimumPasswordLength: '8',
|
minimumPasswordLength: '8',
|
||||||
minimumPasswordLengthMin: '3',
|
minimumPasswordLengthMin: '3',
|
||||||
minimumPasswordLengthMax: '10',
|
minimumPasswordLengthMax: '10',
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Gitlab::Database::Migrations::Runner, :reestablished_active_record_base do
|
RSpec.describe Gitlab::Database::Migrations::Runner, :reestablished_active_record_base do
|
||||||
include Database::MultipleDatabases
|
|
||||||
|
|
||||||
let(:base_result_dir) { Pathname.new(Dir.mktmpdir) }
|
let(:base_result_dir) { Pathname.new(Dir.mktmpdir) }
|
||||||
|
|
||||||
let(:migration_runs) { [] } # This list gets populated as the runner tries to run migrations
|
let(:migration_runs) { [] } # This list gets populated as the runner tries to run migrations
|
||||||
|
|
|
@ -592,6 +592,7 @@ ProjectFeature:
|
||||||
- feature_flags_access_level
|
- feature_flags_access_level
|
||||||
- releases_access_level
|
- releases_access_level
|
||||||
- monitor_access_level
|
- monitor_access_level
|
||||||
|
- infrastructure_access_level
|
||||||
- created_at
|
- created_at
|
||||||
- updated_at
|
- updated_at
|
||||||
ProtectedBranch::MergeAccessLevel:
|
ProtectedBranch::MergeAccessLevel:
|
||||||
|
|
|
@ -23,6 +23,52 @@ RSpec.describe Sidebars::Projects::Menus::InfrastructureMenu do
|
||||||
expect(subject.render?).to be true
|
expect(subject.render?).to be true
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
describe '#link' do
|
describe '#link' do
|
||||||
|
|
|
@ -8,7 +8,7 @@ RSpec.describe ProjectFeaturesCompatibility do
|
||||||
let(:features) do
|
let(:features) do
|
||||||
features_enabled + %w(
|
features_enabled + %w(
|
||||||
repository pages operations container_registry package_registry environments feature_flags releases
|
repository pages operations container_registry package_registry environments feature_flags releases
|
||||||
monitor
|
monitor infrastructure
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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(: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(: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(: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(:maven_package_requests_forwarding).to(:namespace) }
|
||||||
it { is_expected.to delegate_method(:pypi_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) }
|
it { is_expected.to delegate_method(:npm_package_requests_forwarding).to(:namespace) }
|
||||||
|
|
|
@ -69,7 +69,8 @@ RSpec.describe WorkItems::Type do
|
||||||
::WorkItems::Widgets::Hierarchy,
|
::WorkItems::Widgets::Hierarchy,
|
||||||
::WorkItems::Widgets::Labels,
|
::WorkItems::Widgets::Labels,
|
||||||
::WorkItems::Widgets::Assignees,
|
::WorkItems::Widgets::Assignees,
|
||||||
::WorkItems::Widgets::StartAndDueDate
|
::WorkItems::Widgets::StartAndDueDate,
|
||||||
|
::WorkItems::Widgets::Milestone
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -2299,6 +2299,74 @@ RSpec.describe ProjectPolicy do
|
||||||
end
|
end
|
||||||
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
|
describe 'access_security_and_compliance' do
|
||||||
context 'when the "Security & Compliance" is enabled' do
|
context 'when the "Security & Compliance" is enabled' do
|
||||||
before do
|
before do
|
||||||
|
|
|
@ -154,6 +154,68 @@ RSpec.describe 'Create a work item' do
|
||||||
end
|
end
|
||||||
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
|
context 'when the work_items feature flag is disabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(work_items: false)
|
stub_feature_flags(work_items: false)
|
||||||
|
|
|
@ -5,8 +5,11 @@ require 'spec_helper'
|
||||||
RSpec.describe 'Update a work item' do
|
RSpec.describe 'Update a work item' do
|
||||||
include GraphqlHelpers
|
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(: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_it_be(:work_item, refind: true) { create(:work_item, project: project) }
|
||||||
|
|
||||||
let(:work_item_event) { 'CLOSE' }
|
let(:work_item_event) { 'CLOSE' }
|
||||||
|
@ -543,6 +546,91 @@ RSpec.describe 'Update a work item' do
|
||||||
end
|
end
|
||||||
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
|
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(: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) }
|
let_it_be(:work_item) { create(:work_item, work_item_type: test_case, project: project) }
|
||||||
|
|
|
@ -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(:current_user) { create(:user) }
|
||||||
let_it_be(:label1) { create(:label, project: project) }
|
let_it_be(:label1) { create(:label, project: project) }
|
||||||
let_it_be(:label2) { 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(:item1) { create(:work_item, project: project, discussion_locked: true, title: 'item1', labels: [label1]) }
|
||||||
let_it_be(:item2) do
|
let_it_be(:item2) do
|
||||||
|
@ -19,7 +21,8 @@ RSpec.describe 'getting a work item list for a project' do
|
||||||
title: 'item2',
|
title: 'item2',
|
||||||
last_edited_by: current_user,
|
last_edited_by: current_user,
|
||||||
last_edited_at: 1.day.ago,
|
last_edited_at: 1.day.ago,
|
||||||
labels: [label2]
|
labels: [label2],
|
||||||
|
milestone: milestone1
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -55,7 +58,8 @@ RSpec.describe 'getting a work item list for a project' do
|
||||||
:last_edited_by_user,
|
:last_edited_by_user,
|
||||||
last_edited_at: 1.week.ago,
|
last_edited_at: 1.week.ago,
|
||||||
project: project,
|
project: project,
|
||||||
labels: [label1, label2]
|
labels: [label1, label2],
|
||||||
|
milestone: milestone2
|
||||||
)
|
)
|
||||||
|
|
||||||
expect_graphql_errors_to_be_empty
|
expect_graphql_errors_to_be_empty
|
||||||
|
@ -94,6 +98,11 @@ RSpec.describe 'getting a work item list for a project' do
|
||||||
labels { nodes { id } }
|
labels { nodes { id } }
|
||||||
allowsScopedLabels
|
allowsScopedLabels
|
||||||
}
|
}
|
||||||
|
... on WorkItemWidgetMilestone {
|
||||||
|
milestone {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GRAPHQL
|
GRAPHQL
|
||||||
|
|
|
@ -298,6 +298,40 @@ RSpec.describe 'Query.work_item(id)' do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
context 'when an Issue Global ID is provided' do
|
context 'when an Issue Global ID is provided' do
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -145,7 +145,7 @@ RSpec.configure do |config|
|
||||||
config.include NextInstanceOf
|
config.include NextInstanceOf
|
||||||
config.include TestEnv
|
config.include TestEnv
|
||||||
config.include FileReadHelpers
|
config.include FileReadHelpers
|
||||||
config.include Database::MultipleDatabases
|
config.include Database::MultipleDatabasesHelpers
|
||||||
config.include Database::WithoutCheckConstraint
|
config.include Database::WithoutCheckConstraint
|
||||||
config.include Devise::Test::ControllerHelpers, type: :controller
|
config.include Devise::Test::ControllerHelpers, type: :controller
|
||||||
config.include Devise::Test::ControllerHelpers, type: :view
|
config.include Devise::Test::ControllerHelpers, type: :view
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Database
|
module Database
|
||||||
module MultipleDatabases
|
module MultipleDatabasesHelpers
|
||||||
def skip_if_multiple_databases_not_setup
|
def skip_if_multiple_databases_not_setup
|
||||||
skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
|
skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
|
||||||
end
|
end
|
||||||
|
@ -52,17 +52,17 @@ module Database
|
||||||
#
|
#
|
||||||
# rubocop:disable Database/MultipleDatabases
|
# rubocop:disable Database/MultipleDatabases
|
||||||
def with_reestablished_active_record_base(reconnect: true)
|
def with_reestablished_active_record_base(reconnect: true)
|
||||||
connection_classes = ActiveRecord::Base.connection_handler.connection_pool_names.map(&:constantize).to_h do |klass|
|
connection_classes = ActiveRecord::Base
|
||||||
[klass, klass.connection_db_config]
|
.connection_handler
|
||||||
end
|
.connection_pool_names
|
||||||
|
.map(&:constantize)
|
||||||
|
.index_with(&:connection_db_config)
|
||||||
|
|
||||||
original_handler = ActiveRecord::Base.connection_handler
|
original_handler = ActiveRecord::Base.connection_handler
|
||||||
new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
|
new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
|
||||||
ActiveRecord::Base.connection_handler = new_handler
|
ActiveRecord::Base.connection_handler = new_handler
|
||||||
|
|
||||||
if reconnect
|
connection_classes.each { |klass, db_config| klass.establish_connection(db_config) } if reconnect
|
||||||
connection_classes.each { |klass, db_config| klass.establish_connection(db_config) }
|
|
||||||
end
|
|
||||||
|
|
||||||
yield
|
yield
|
||||||
ensure
|
ensure
|
||||||
|
@ -95,9 +95,12 @@ module Database
|
||||||
module ActiveRecordBaseEstablishConnection
|
module ActiveRecordBaseEstablishConnection
|
||||||
def establish_connection(*args)
|
def establish_connection(*args)
|
||||||
# rubocop:disable Database/MultipleDatabases
|
# rubocop:disable Database/MultipleDatabases
|
||||||
if connected? && connection&.transaction_open? && ActiveRecord::Base.connection_handler == ActiveRecord::Base.default_connection_handler
|
if connected? &&
|
||||||
raise "Cannot re-establish '#{self}.establish_connection' within an open transaction (#{connection&.open_transactions.to_i}). " \
|
connection&.transaction_open? &&
|
||||||
"Use `with_reestablished_active_record_base` instead or add `:reestablished_active_record_base` to rspec context."
|
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
|
end
|
||||||
# rubocop:enable Database/MultipleDatabases
|
# rubocop:enable Database/MultipleDatabases
|
||||||
|
|
||||||
|
@ -106,56 +109,4 @@ module Database
|
||||||
end
|
end
|
||||||
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
|
ActiveRecord::Base.singleton_class.prepend(::Database::ActiveRecordBaseEstablishConnection) # rubocop:disable Database/MultipleDatabases
|
|
@ -16,14 +16,42 @@ RSpec.configure do |config|
|
||||||
schema_migrate_down!
|
schema_migrate_down!
|
||||||
end
|
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
|
# Each example may call `migrate!`, so we must ensure we are migrated down every time
|
||||||
config.before(:each, :migration) do
|
config.before(:each, :migration) do
|
||||||
use_fake_application_settings
|
use_fake_application_settings
|
||||||
|
|
||||||
schema_migrate_down!
|
schema_migrate_down!
|
||||||
end
|
end
|
||||||
|
|
||||||
config.after(:context, :migration) do
|
|
||||||
Gitlab::CurrentSettings.clear_in_memory_application_settings!
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -161,9 +161,10 @@ RSpec.shared_examples 'wiki model' do
|
||||||
let(:wiki_pages) { subject.list_pages }
|
let(:wiki_pages) { subject.list_pages }
|
||||||
|
|
||||||
before do
|
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('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
|
end
|
||||||
|
|
||||||
it 'returns an array of WikiPage instances' do
|
it 'returns an array of WikiPage instances' do
|
||||||
|
@ -183,13 +184,47 @@ RSpec.shared_examples 'wiki model' do
|
||||||
|
|
||||||
context 'with limit option' do
|
context 'with limit option' do
|
||||||
it 'returns limited set of pages' 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
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with sorting options' do
|
context 'with sorting options' do
|
||||||
it 'returns pages sorted by title by default' 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.map(&:title)).to eq(pages)
|
||||||
expect(subject.list_pages(direction: 'desc').map(&:title)).to eq(pages.reverse)
|
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) }
|
let(:pages) { subject.list_pages(load_content: true) }
|
||||||
|
|
||||||
it 'loads WikiPage content' do
|
it 'loads WikiPage content' do
|
||||||
expect(pages.first.content).to eq('This is an index3')
|
expect(pages.first.content).to eq('This is an index')
|
||||||
expect(pages.second.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 index2')
|
expect(pages.third.content).to eq('This is an index3')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe 'Database::MultipleDatabases' do
|
RSpec.describe 'Database::MultipleDatabasesHelpers' do
|
||||||
let(:query) do
|
let(:query) do
|
||||||
<<~SQL
|
<<~SQL
|
||||||
WITH cte AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (SELECT 1) SELECT 1;
|
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
|
context 'when reconnect is false' do
|
||||||
it 'does raise exception' do
|
it 'does raise exception' do
|
||||||
with_reestablished_active_record_base(reconnect: false) 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
|
end
|
||||||
end
|
end
|
Loading…
Reference in New Issue