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'
|
||||
|
||||
# Gitaly GRPC protocol definitions
|
||||
gem 'gitaly', '~> 15.4.0-rc2'
|
||||
gem 'gitaly', '~> 15.5.0'
|
||||
|
||||
# KAS GRPC protocol definitions
|
||||
gem 'kas-grpc', '~> 0.0.2'
|
||||
|
|
|
@ -198,7 +198,7 @@
|
|||
{"name":"gettext_i18n_rails","version":"1.8.0","platform":"ruby","checksum":"95e5cf8440b1e08705b27f2bccb56143272c5a7a0dabcf54ea1bd701140a496f"},
|
||||
{"name":"gettext_i18n_rails_js","version":"1.3.0","platform":"ruby","checksum":"5d10afe4be3639bff78c50a56768c20f39aecdabc580c08aa45573911c2bd687"},
|
||||
{"name":"git","version":"1.11.0","platform":"ruby","checksum":"7e95ba4da8298a0373ef1a6862aa22007d761f3c8274b675aa787966fecea0f1"},
|
||||
{"name":"gitaly","version":"15.4.0.pre.rc2","platform":"ruby","checksum":"48764528a730605a46f00cf86c7cfcb92d25f4f3d8cb9e09557baac3e9f3f8e3"},
|
||||
{"name":"gitaly","version":"15.5.0","platform":"ruby","checksum":"d85dd4890a1f0fd95f935c848bcedf03f19b78872f20f04b9811e602bea4ef42"},
|
||||
{"name":"github-markup","version":"1.7.0","platform":"ruby","checksum":"97eb27c70662d9cc1d5997cd6c99832026fae5d4913b5dce1ce6c9f65078e69d"},
|
||||
{"name":"gitlab","version":"4.16.1","platform":"ruby","checksum":"13fd7059cbdad5a1a21b15fa2cf9070b97d92e27f8c688581fe3d84dc038074f"},
|
||||
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
|
||||
|
|
|
@ -547,7 +547,7 @@ GEM
|
|||
rails (>= 3.2.0)
|
||||
git (1.11.0)
|
||||
rchardet (~> 1.8)
|
||||
gitaly (15.4.0.pre.rc2)
|
||||
gitaly (15.5.0)
|
||||
grpc (~> 1.0)
|
||||
github-markup (1.7.0)
|
||||
gitlab (4.16.1)
|
||||
|
@ -1626,7 +1626,7 @@ DEPENDENCIES
|
|||
gettext (~> 3.3)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.3)
|
||||
gitaly (~> 15.4.0.pre.rc2)
|
||||
gitaly (~> 15.5.0)
|
||||
github-markup (~> 1.7.0)
|
||||
gitlab-chronic (~> 0.10.5)
|
||||
gitlab-dangerfiles (~> 3.6.1)
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"WorkItemWidgetHierarchy",
|
||||
"WorkItemWidgetIteration",
|
||||
"WorkItemWidgetLabels",
|
||||
"WorkItemWidgetMilestone",
|
||||
"WorkItemWidgetStartAndDueDate",
|
||||
"WorkItemWidgetStatus",
|
||||
"WorkItemWidgetWeight"
|
||||
|
|
|
@ -44,6 +44,7 @@ export default {
|
|||
'signupEnabled',
|
||||
'requireAdminApprovalAfterUserSignup',
|
||||
'sendUserConfirmationEmail',
|
||||
'emailConfirmationSetting',
|
||||
'minimumPasswordLength',
|
||||
'minimumPasswordLengthMin',
|
||||
'minimumPasswordLengthMax',
|
||||
|
@ -66,6 +67,7 @@ export default {
|
|||
signupEnabled: this.signupEnabled,
|
||||
requireAdminApproval: this.requireAdminApprovalAfterUserSignup,
|
||||
sendConfirmationEmail: this.sendUserConfirmationEmail,
|
||||
emailConfirmationSetting: this.emailConfirmationSetting,
|
||||
minimumPasswordLength: this.minimumPasswordLength,
|
||||
minimumPasswordLengthMin: this.minimumPasswordLengthMin,
|
||||
minimumPasswordLengthMax: this.minimumPasswordLengthMax,
|
||||
|
@ -199,6 +201,15 @@ export default {
|
|||
signupEnabledLabel: s__('ApplicationSettings|Sign-up enabled'),
|
||||
requireAdminApprovalLabel: s__('ApplicationSettings|Require admin approval for new sign-ups'),
|
||||
sendConfirmationEmailLabel: s__('ApplicationSettings|Send confirmation email on sign-up'),
|
||||
emailConfirmationSettingsLabel: s__('ApplicationSettings|Email confirmation settings'),
|
||||
emailConfirmationSettingsOffLabel: s__('ApplicationSettings|Off'),
|
||||
emailConfirmationSettingsOffHelpText: s__(
|
||||
'ApplicationSettings|New users can sign up without confirming their email address.',
|
||||
),
|
||||
emailConfirmationSettingsHardLabel: s__('ApplicationSettings|Hard'),
|
||||
emailConfirmationSettingsHardHelpText: s__(
|
||||
'ApplicationSettings|Send a confirmation email during sign up. New users must confirm their email address before they can log in.',
|
||||
),
|
||||
minimumPasswordLengthLabel: s__(
|
||||
'ApplicationSettings|Minimum password length (number of characters)',
|
||||
),
|
||||
|
@ -276,6 +287,24 @@ export default {
|
|||
:label="$options.i18n.sendConfirmationEmailLabel"
|
||||
/>
|
||||
|
||||
<gl-form-group :label="$options.i18n.emailConfirmationSettingsLabel">
|
||||
<gl-form-radio-group
|
||||
v-model="form.emailConfirmationSetting"
|
||||
name="application_setting[email_confirmation_setting]"
|
||||
>
|
||||
<gl-form-radio value="hard">
|
||||
{{ $options.i18n.emailConfirmationSettingsHardLabel }}
|
||||
|
||||
<template #help> {{ $options.i18n.emailConfirmationSettingsHardHelpText }} </template>
|
||||
</gl-form-radio>
|
||||
<gl-form-radio value="off">
|
||||
{{ $options.i18n.emailConfirmationSettingsOffLabel }}
|
||||
|
||||
<template #help> {{ $options.i18n.emailConfirmationSettingsOffHelpText }} </template>
|
||||
</gl-form-radio>
|
||||
</gl-form-radio-group>
|
||||
</gl-form-group>
|
||||
|
||||
<gl-form-group
|
||||
:label="$options.i18n.userCapLabel"
|
||||
:description="$options.i18n.userCapDescription"
|
||||
|
|
|
@ -440,7 +440,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
def operations_feature_attributes
|
||||
if Feature.enabled?(:split_operations_visibility_permissions, project)
|
||||
%i[
|
||||
environments_access_level feature_flags_access_level monitor_access_level
|
||||
environments_access_level feature_flags_access_level monitor_access_level infrastructure_access_level
|
||||
]
|
||||
else
|
||||
%i[operations_access_level]
|
||||
|
|
|
@ -33,6 +33,9 @@ module Mutations
|
|||
argument :labels_widget, ::Types::WorkItems::Widgets::LabelsUpdateInputType,
|
||||
required: false,
|
||||
description: 'Input for labels widget.'
|
||||
argument :milestone_widget, ::Types::WorkItems::Widgets::MilestoneInputType,
|
||||
required: false,
|
||||
description: 'Input for milestone widget.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,6 +22,9 @@ module Mutations
|
|||
argument :hierarchy_widget, ::Types::WorkItems::Widgets::HierarchyCreateInputType,
|
||||
required: false,
|
||||
description: 'Input for hierarchy widget.'
|
||||
argument :milestone_widget, ::Types::WorkItems::Widgets::MilestoneInputType,
|
||||
required: false,
|
||||
description: 'Input for milestone widget.'
|
||||
argument :project_path, GraphQL::Types::ID,
|
||||
required: true,
|
||||
description: 'Full path of the project the work item is associated with.'
|
||||
|
|
|
@ -55,7 +55,8 @@ module Resolvers
|
|||
last_edited_by: :last_edited_by,
|
||||
assignees: :assignees,
|
||||
parent: :work_item_parent,
|
||||
labels: :labels
|
||||
labels: :labels,
|
||||
milestone: :milestone
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@ module Types
|
|||
::Types::WorkItems::Widgets::HierarchyType,
|
||||
::Types::WorkItems::Widgets::LabelsType,
|
||||
::Types::WorkItems::Widgets::AssigneesType,
|
||||
::Types::WorkItems::Widgets::StartAndDueDateType
|
||||
::Types::WorkItems::Widgets::StartAndDueDateType,
|
||||
::Types::WorkItems::Widgets::MilestoneType
|
||||
].freeze
|
||||
|
||||
def self.ce_orphan_types
|
||||
|
@ -38,6 +39,8 @@ module Types
|
|||
::Types::WorkItems::Widgets::LabelsType
|
||||
when ::WorkItems::Widgets::StartAndDueDate
|
||||
::Types::WorkItems::Widgets::StartAndDueDateType
|
||||
when ::WorkItems::Widgets::Milestone
|
||||
::Types::WorkItems::Widgets::MilestoneType
|
||||
else
|
||||
raise "Unknown GraphQL type for widget #{object}"
|
||||
end
|
||||
|
|
|
@ -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_secret_access_key,
|
||||
:email_author_in_body,
|
||||
:email_confirmation_setting,
|
||||
:enabled_git_access_protocol,
|
||||
:enforce_terms,
|
||||
:error_tracking_enabled,
|
||||
|
@ -544,6 +545,7 @@ module ApplicationSettingsHelper
|
|||
signup_enabled: @application_setting[:signup_enabled].to_s,
|
||||
require_admin_approval_after_user_signup: @application_setting[:require_admin_approval_after_user_signup].to_s,
|
||||
send_user_confirmation_email: @application_setting[:send_user_confirmation_email].to_s,
|
||||
email_confirmation_setting: @application_setting[:email_confirmation_setting].to_s,
|
||||
minimum_password_length: @application_setting[:minimum_password_length],
|
||||
minimum_password_length_min: ApplicationSetting::DEFAULT_MINIMUM_PASSWORD_LENGTH,
|
||||
minimum_password_length_max: Devise.password_length.max,
|
||||
|
|
|
@ -20,6 +20,7 @@ class ApplicationSetting < ApplicationRecord
|
|||
'Admin Area > Settings > General > Kroki'
|
||||
|
||||
enum whats_new_variant: { all_tiers: 0, current_tier: 1, disabled: 2 }, _prefix: true
|
||||
enum email_confirmation_setting: { off: 0, soft: 1, hard: 2 }
|
||||
|
||||
add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption) ? :optional : :required }
|
||||
add_authentication_token_field :health_check_access_token
|
||||
|
|
|
@ -110,6 +110,10 @@ module ProjectFeaturesCompatibility
|
|||
write_feature_attribute_string(:releases_access_level, value)
|
||||
end
|
||||
|
||||
def infrastructure_access_level=(value)
|
||||
write_feature_attribute_string(:infrastructure_access_level, value)
|
||||
end
|
||||
|
||||
# TODO: Remove this method after we drop support for project create/edit APIs to set the
|
||||
# container_registry_enabled attribute. They can instead set the container_registry_access_level
|
||||
# attribute.
|
||||
|
|
|
@ -4,11 +4,6 @@ class NamespaceSetting < ApplicationRecord
|
|||
include CascadingNamespaceSettingAttribute
|
||||
include Sanitizable
|
||||
include ChronicDurationAttribute
|
||||
include IgnorableColumns
|
||||
|
||||
ignore_columns %i[exclude_from_free_user_cap include_for_free_user_cap_preview],
|
||||
remove_with: '15.5',
|
||||
remove_after: '2022-09-23'
|
||||
|
||||
cascading_attr :delayed_project_removal
|
||||
|
||||
|
|
|
@ -451,7 +451,7 @@ class Project < ApplicationRecord
|
|||
:metrics_dashboard_access_level, :analytics_access_level,
|
||||
:operations_access_level, :security_and_compliance_access_level,
|
||||
:container_registry_access_level, :environments_access_level, :feature_flags_access_level,
|
||||
:monitor_access_level, :releases_access_level,
|
||||
:monitor_access_level, :releases_access_level, :infrastructure_access_level,
|
||||
to: :project_feature, allow_nil: true
|
||||
|
||||
delegate :show_default_award_emojis, :show_default_award_emojis=,
|
||||
|
|
|
@ -25,6 +25,7 @@ class ProjectFeature < ApplicationRecord
|
|||
environments
|
||||
feature_flags
|
||||
releases
|
||||
infrastructure
|
||||
].freeze
|
||||
|
||||
EXPORTABLE_FEATURES = (FEATURES - [:security_and_compliance, :pages]).freeze
|
||||
|
|
|
@ -190,7 +190,7 @@ class Wiki
|
|||
end
|
||||
|
||||
def empty?
|
||||
!repository_exists? || list_page_paths.empty?
|
||||
!repository_exists? || list_page_paths(limit: 1).empty?
|
||||
end
|
||||
|
||||
def exists?
|
||||
|
@ -207,9 +207,9 @@ class Wiki
|
|||
#
|
||||
# Returns an Array of GitLab WikiPage instances or an
|
||||
# empty Array if this Wiki has no pages.
|
||||
def list_pages(limit: 0, direction: DIRECTION_ASC, load_content: false)
|
||||
def list_pages(direction: DIRECTION_ASC, load_content: false, limit: 0, offset: 0)
|
||||
create_wiki_repository unless repository_exists?
|
||||
list_pages_with_repository_rpcs(limit: limit, direction: direction, load_content: load_content)
|
||||
list_pages_with_repository_rpcs(direction: direction, load_content: load_content, limit: limit, offset: offset)
|
||||
end
|
||||
|
||||
def sidebar_entries(limit: Gitlab::WikiPages::MAX_SIDEBAR_PAGES, **options)
|
||||
|
@ -457,7 +457,7 @@ class Wiki
|
|||
escaped_path = RE2::Regexp.escape(sluggified_title(title))
|
||||
path_regexp = Gitlab::EncodingHelper.encode_utf8_no_detect("(?i)^#{escaped_path}\\.(#{file_extension_regexp})$")
|
||||
|
||||
matched_files = repository.search_files_by_regexp(path_regexp, version)
|
||||
matched_files = repository.search_files_by_regexp(path_regexp, version, limit: 1)
|
||||
return if matched_files.blank?
|
||||
|
||||
Gitlab::EncodingHelper.encode_utf8_no_detect(matched_files.first)
|
||||
|
@ -509,15 +509,15 @@ class Wiki
|
|||
path.sub(/\.[^.]+\z/, "")
|
||||
end
|
||||
|
||||
def list_page_paths
|
||||
def list_page_paths(limit: 0, offset: 0)
|
||||
return [] if repository.empty?
|
||||
|
||||
path_regexp = Gitlab::EncodingHelper.encode_utf8_no_detect("(?i)\\.(#{file_extension_regexp})$")
|
||||
repository.search_files_by_regexp(path_regexp, default_branch)
|
||||
repository.search_files_by_regexp(path_regexp, default_branch, limit: limit, offset: offset)
|
||||
end
|
||||
|
||||
def list_pages_with_repository_rpcs(limit:, direction:, load_content:)
|
||||
paths = list_page_paths
|
||||
def list_pages_with_repository_rpcs(direction:, load_content:, limit:, offset:)
|
||||
paths = list_page_paths(limit: limit, offset: offset)
|
||||
return [] if paths.empty?
|
||||
|
||||
pages = paths.map do |path|
|
||||
|
|
|
@ -21,11 +21,13 @@ module WorkItems
|
|||
}.freeze
|
||||
|
||||
WIDGETS_FOR_TYPE = {
|
||||
issue: [Widgets::Assignees, Widgets::Labels, Widgets::Description, Widgets::Hierarchy, Widgets::StartAndDueDate],
|
||||
issue: [Widgets::Assignees, Widgets::Labels, Widgets::Description, Widgets::Hierarchy, Widgets::StartAndDueDate,
|
||||
Widgets::Milestone],
|
||||
incident: [Widgets::Description, Widgets::Hierarchy],
|
||||
test_case: [Widgets::Description],
|
||||
requirement: [Widgets::Description],
|
||||
task: [Widgets::Assignees, Widgets::Labels, Widgets::Description, Widgets::Hierarchy, Widgets::StartAndDueDate]
|
||||
task: [Widgets::Assignees, Widgets::Labels, Widgets::Description, Widgets::Hierarchy, Widgets::StartAndDueDate,
|
||||
Widgets::Milestone]
|
||||
}.freeze
|
||||
|
||||
WI_TYPES_WITH_CREATED_HEADER = %w[issue incident].freeze
|
||||
|
|
|
@ -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
|
||||
feature_flags
|
||||
releases
|
||||
infrastructure
|
||||
]
|
||||
|
||||
features.each do |f|
|
||||
|
@ -409,6 +410,14 @@ class ProjectPolicy < BasePolicy
|
|||
prevent(*create_read_update_admin_destroy(:alert_management_alert))
|
||||
end
|
||||
|
||||
rule { split_operations_visibility_permissions & infrastructure_disabled }.policy do
|
||||
prevent(*create_read_update_admin_destroy(:terraform_state))
|
||||
prevent(*create_read_update_admin_destroy(:cluster))
|
||||
prevent(:read_pod_logs)
|
||||
prevent(:read_prometheus)
|
||||
prevent(:admin_project_google_cloud)
|
||||
end
|
||||
|
||||
rule { can?(:metrics_dashboard) }.policy do
|
||||
enable :read_prometheus
|
||||
enable :read_deployment
|
||||
|
|
|
@ -30,6 +30,13 @@ module WorkItems
|
|||
error(e.message, :unprocessable_entity)
|
||||
end
|
||||
|
||||
def before_create(work_item)
|
||||
execute_widgets(work_item: work_item, callback: :before_create_callback,
|
||||
widget_params: @widget_params)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def transaction_create(work_item)
|
||||
super.tap do |save_result|
|
||||
if save_result
|
||||
|
|
|
@ -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_notice_before_days integer DEFAULT 7 NOT NULL,
|
||||
product_analytics_enabled boolean DEFAULT false NOT NULL,
|
||||
email_confirmation_setting smallint DEFAULT 2,
|
||||
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
||||
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
|
||||
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
||||
|
|
|
@ -5774,6 +5774,7 @@ Input type: `WorkItemCreateInput`
|
|||
| <a id="mutationworkitemcreateconfidential"></a>`confidential` | [`Boolean`](#boolean) | Sets the work item confidentiality. |
|
||||
| <a id="mutationworkitemcreatedescription"></a>`description` | [`String`](#string) | Description of the work item. |
|
||||
| <a id="mutationworkitemcreatehierarchywidget"></a>`hierarchyWidget` | [`WorkItemWidgetHierarchyCreateInput`](#workitemwidgethierarchycreateinput) | Input for hierarchy widget. |
|
||||
| <a id="mutationworkitemcreatemilestonewidget"></a>`milestoneWidget` | [`WorkItemWidgetMilestoneInput`](#workitemwidgetmilestoneinput) | Input for milestone widget. |
|
||||
| <a id="mutationworkitemcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project the work item is associated with. |
|
||||
| <a id="mutationworkitemcreatetitle"></a>`title` | [`String!`](#string) | Title of the work item. |
|
||||
| <a id="mutationworkitemcreateworkitemtypeid"></a>`workItemTypeId` | [`WorkItemsTypeID!`](#workitemstypeid) | Global ID of a work item type. |
|
||||
|
@ -5887,6 +5888,7 @@ Input type: `WorkItemUpdateInput`
|
|||
| <a id="mutationworkitemupdateid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
|
||||
| <a id="mutationworkitemupdateiterationwidget"></a>`iterationWidget` | [`WorkItemWidgetIterationInput`](#workitemwidgetiterationinput) | Input for iteration widget. |
|
||||
| <a id="mutationworkitemupdatelabelswidget"></a>`labelsWidget` | [`WorkItemWidgetLabelsUpdateInput`](#workitemwidgetlabelsupdateinput) | Input for labels widget. |
|
||||
| <a id="mutationworkitemupdatemilestonewidget"></a>`milestoneWidget` | [`WorkItemWidgetMilestoneInput`](#workitemwidgetmilestoneinput) | Input for milestone widget. |
|
||||
| <a id="mutationworkitemupdatestartandduedatewidget"></a>`startAndDueDateWidget` | [`WorkItemWidgetStartAndDueDateUpdateInput`](#workitemwidgetstartandduedateupdateinput) | Input for start and due date widget. |
|
||||
| <a id="mutationworkitemupdatestateevent"></a>`stateEvent` | [`WorkItemStateEvent`](#workitemstateevent) | Close or reopen a work item. |
|
||||
| <a id="mutationworkitemupdatestatuswidget"></a>`statusWidget` | [`StatusInput`](#statusinput) | Input for status widget. |
|
||||
|
@ -19968,6 +19970,17 @@ Represents the labels widget.
|
|||
| <a id="workitemwidgetlabelslabels"></a>`labels` | [`LabelConnection`](#labelconnection) | Labels assigned to the work item. (see [Connections](#connections)) |
|
||||
| <a id="workitemwidgetlabelstype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
|
||||
|
||||
### `WorkItemWidgetMilestone`
|
||||
|
||||
Represents a milestone widget.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="workitemwidgetmilestonemilestone"></a>`milestone` | [`Milestone`](#milestone) | Milestone of the work item. |
|
||||
| <a id="workitemwidgetmilestonetype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
|
||||
|
||||
### `WorkItemWidgetStartAndDueDate`
|
||||
|
||||
Represents a start and due date widget.
|
||||
|
@ -22044,6 +22057,7 @@ Type of a work item widget.
|
|||
| <a id="workitemwidgettypehierarchy"></a>`HIERARCHY` | Hierarchy widget. |
|
||||
| <a id="workitemwidgettypeiteration"></a>`ITERATION` | Iteration widget. |
|
||||
| <a id="workitemwidgettypelabels"></a>`LABELS` | Labels widget. |
|
||||
| <a id="workitemwidgettypemilestone"></a>`MILESTONE` | Milestone widget. |
|
||||
| <a id="workitemwidgettypestart_and_due_date"></a>`START_AND_DUE_DATE` | Start And Due Date widget. |
|
||||
| <a id="workitemwidgettypestatus"></a>`STATUS` | Status widget. |
|
||||
| <a id="workitemwidgettypeweight"></a>`WEIGHT` | Weight widget. |
|
||||
|
@ -23314,6 +23328,7 @@ Implementations:
|
|||
- [`WorkItemWidgetHierarchy`](#workitemwidgethierarchy)
|
||||
- [`WorkItemWidgetIteration`](#workitemwidgetiteration)
|
||||
- [`WorkItemWidgetLabels`](#workitemwidgetlabels)
|
||||
- [`WorkItemWidgetMilestone`](#workitemwidgetmilestone)
|
||||
- [`WorkItemWidgetStartAndDueDate`](#workitemwidgetstartandduedate)
|
||||
- [`WorkItemWidgetStatus`](#workitemwidgetstatus)
|
||||
- [`WorkItemWidgetWeight`](#workitemwidgetweight)
|
||||
|
@ -23847,6 +23862,7 @@ A time-frame defined as a closed inclusive range of two dates.
|
|||
| <a id="workitemupdatedtaskinputhierarchywidget"></a>`hierarchyWidget` | [`WorkItemWidgetHierarchyUpdateInput`](#workitemwidgethierarchyupdateinput) | Input for hierarchy widget. |
|
||||
| <a id="workitemupdatedtaskinputid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
|
||||
| <a id="workitemupdatedtaskinputlabelswidget"></a>`labelsWidget` | [`WorkItemWidgetLabelsUpdateInput`](#workitemwidgetlabelsupdateinput) | Input for labels widget. |
|
||||
| <a id="workitemupdatedtaskinputmilestonewidget"></a>`milestoneWidget` | [`WorkItemWidgetMilestoneInput`](#workitemwidgetmilestoneinput) | Input for milestone widget. |
|
||||
| <a id="workitemupdatedtaskinputstartandduedatewidget"></a>`startAndDueDateWidget` | [`WorkItemWidgetStartAndDueDateUpdateInput`](#workitemwidgetstartandduedateupdateinput) | Input for start and due date widget. |
|
||||
| <a id="workitemupdatedtaskinputstateevent"></a>`stateEvent` | [`WorkItemStateEvent`](#workitemstateevent) | Close or reopen a work item. |
|
||||
| <a id="workitemupdatedtaskinputtitle"></a>`title` | [`String`](#string) | Title of the work item. |
|
||||
|
@ -23901,6 +23917,14 @@ A time-frame defined as a closed inclusive range of two dates.
|
|||
| <a id="workitemwidgetlabelsupdateinputaddlabelids"></a>`addLabelIds` | [`[LabelID!]`](#labelid) | Global IDs of labels to be added to the work item. |
|
||||
| <a id="workitemwidgetlabelsupdateinputremovelabelids"></a>`removeLabelIds` | [`[LabelID!]`](#labelid) | Global IDs of labels to be removed from the work item. |
|
||||
|
||||
### `WorkItemWidgetMilestoneInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="workitemwidgetmilestoneinputmilestoneid"></a>`milestoneId` | [`MilestoneID`](#milestoneid) | Milestone to assign to the work item. |
|
||||
|
||||
### `WorkItemWidgetStartAndDueDateUpdateInput`
|
||||
|
||||
#### Arguments
|
||||
|
|
|
@ -1054,19 +1054,19 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def search_files_by_name(query, ref)
|
||||
def search_files_by_name(query, ref, limit: 0, offset: 0)
|
||||
safe_query = query.sub(%r{^/*}, "")
|
||||
ref ||= root_ref
|
||||
|
||||
return [] if empty? || safe_query.blank?
|
||||
|
||||
gitaly_repository_client.search_files_by_name(ref, safe_query).map do |file|
|
||||
gitaly_repository_client.search_files_by_name(ref, safe_query, limit: limit, offset: offset).map do |file|
|
||||
Gitlab::EncodingHelper.encode_utf8(file)
|
||||
end
|
||||
end
|
||||
|
||||
def search_files_by_regexp(filter, ref = 'HEAD')
|
||||
gitaly_repository_client.search_files_by_regexp(ref, filter).map do |file|
|
||||
def search_files_by_regexp(filter, ref = 'HEAD', limit: 0, offset: 0)
|
||||
gitaly_repository_client.search_files_by_regexp(ref, filter, limit: limit, offset: offset).map do |file|
|
||||
Gitlab::EncodingHelper.encode_utf8(file)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -303,8 +303,8 @@ module Gitlab
|
|||
GitalyClient.call(@storage, :repository_service, :get_raw_changes, request, timeout: GitalyClient.fast_timeout)
|
||||
end
|
||||
|
||||
def search_files_by_name(ref, query)
|
||||
request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: query)
|
||||
def search_files_by_name(ref, query, limit: 0, offset: 0)
|
||||
request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: query, limit: limit, offset: offset)
|
||||
GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files)
|
||||
end
|
||||
|
||||
|
@ -314,8 +314,8 @@ module Gitlab
|
|||
search_results_from_response(response, options)
|
||||
end
|
||||
|
||||
def search_files_by_regexp(ref, filter)
|
||||
request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: '.', filter: filter)
|
||||
def search_files_by_regexp(ref, filter, limit: 0, offset: 0)
|
||||
request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: '.', filter: filter, limit: limit, offset: offset)
|
||||
GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files)
|
||||
end
|
||||
|
||||
|
|
|
@ -302,6 +302,7 @@ included_attributes:
|
|||
- :environments_access_level
|
||||
- :feature_flags_access_level
|
||||
- :releases_access_level
|
||||
- :infrastructure_access_level
|
||||
prometheus_metrics:
|
||||
- :created_at
|
||||
- :updated_at
|
||||
|
@ -717,6 +718,7 @@ included_attributes:
|
|||
- :environments_access_level
|
||||
- :feature_flags_access_level
|
||||
- :releases_access_level
|
||||
- :infrastructure_access_level
|
||||
- :allow_merge_on_skipped_pipeline
|
||||
- :auto_devops_deploy_strategy
|
||||
- :auto_devops_enabled
|
||||
|
|
|
@ -20,6 +20,10 @@ module Sidebars
|
|||
# Push Rules are the only group setting that can also be edited by maintainers.
|
||||
# Create an empty sub-menu here and EE adds Repository menu item (with only Push Rules).
|
||||
return true
|
||||
elsif Gitlab.ee? && can?(context.current_user, :read_billing, context.group)
|
||||
# Billing is the only group setting that is visible to auditors.
|
||||
# Create an empty sub-menu here and EE adds Settings menu item (with only Billing).
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
|
|
|
@ -6,7 +6,7 @@ module Sidebars
|
|||
class InfrastructureMenu < ::Sidebars::Menu
|
||||
override :configure_menu_items
|
||||
def configure_menu_items
|
||||
return false unless context.project.feature_available?(:operations, context.current_user)
|
||||
return false unless feature_enabled?
|
||||
|
||||
add_item(kubernetes_menu_item)
|
||||
add_item(terraform_menu_item)
|
||||
|
@ -34,6 +34,14 @@ module Sidebars
|
|||
|
||||
private
|
||||
|
||||
def feature_enabled?
|
||||
if ::Feature.enabled?(:split_operations_visibility_permissions, context.project)
|
||||
context.project.feature_available?(:infrastructure, context.current_user)
|
||||
else
|
||||
context.project.feature_available?(:operations, context.current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def kubernetes_menu_item
|
||||
unless can?(context.current_user, :read_cluster, context.project)
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :kubernetes)
|
||||
|
|
|
@ -4717,6 +4717,9 @@ msgstr ""
|
|||
msgid "ApplicationSettings|Domain denylist"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApplicationSettings|Email confirmation settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApplicationSettings|Email restrictions"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4735,9 +4738,18 @@ msgstr ""
|
|||
msgid "ApplicationSettings|Enter denylist manually"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApplicationSettings|Hard"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApplicationSettings|Minimum password length (number of characters)"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApplicationSettings|New users can sign up without confirming their email address."
|
||||
msgstr ""
|
||||
|
||||
msgid "ApplicationSettings|Off"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApplicationSettings|Only users with e-mail addresses that match these domain(s) can sign up. Wildcards allowed. Use separate lines for multiple entries. Example: domain.com, *.domain.com"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4765,6 +4777,9 @@ msgstr ""
|
|||
msgid "ApplicationSettings|See %{linkStart}password policy guidelines%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ApplicationSettings|Send a confirmation email during sign up. New users must confirm their email address before they can log in."
|
||||
msgstr ""
|
||||
|
||||
msgid "ApplicationSettings|Send confirmation email on sign-up"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'gitlab-qa', '~> 8', '>= 8.8.0', require: 'gitlab/qa'
|
||||
gem 'gitlab-qa', '~> 8', '>= 8.9.0', require: 'gitlab/qa'
|
||||
gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile
|
||||
gem 'allure-rspec', '~> 2.18.0'
|
||||
gem 'capybara', '~> 3.37.1'
|
||||
|
|
|
@ -100,7 +100,7 @@ GEM
|
|||
gitlab (4.18.0)
|
||||
httparty (~> 0.18)
|
||||
terminal-table (>= 1.5.1)
|
||||
gitlab-qa (8.8.0)
|
||||
gitlab-qa (8.9.0)
|
||||
activesupport (~> 6.1)
|
||||
gitlab (~> 4.18.0)
|
||||
http (~> 5.0)
|
||||
|
@ -314,7 +314,7 @@ DEPENDENCIES
|
|||
faraday-retry (~> 2.0)
|
||||
fog-core (= 2.1.0)
|
||||
fog-google (~> 1.19)
|
||||
gitlab-qa (~> 8, >= 8.8.0)
|
||||
gitlab-qa (~> 8, >= 8.9.0)
|
||||
influxdb-client (~> 2.7)
|
||||
knapsack (~> 4.0)
|
||||
nokogiri (~> 1.13, >= 1.13.9)
|
||||
|
|
|
@ -27,6 +27,7 @@ module RuboCop
|
|||
environments
|
||||
feature_flags
|
||||
releases
|
||||
infrastructure
|
||||
].freeze
|
||||
EE_FEATURES = %i[requirements].freeze
|
||||
ALL_FEATURES = (FEATURES + EE_FEATURES).freeze
|
||||
|
|
|
@ -921,6 +921,7 @@ RSpec.describe ProjectsController do
|
|||
feature_flags_access_level
|
||||
releases_access_level
|
||||
monitor_access_level
|
||||
infrastructure_access_level
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ FactoryBot.define do
|
|||
environments_access_level { ProjectFeature::ENABLED }
|
||||
feature_flags_access_level { ProjectFeature::ENABLED }
|
||||
releases_access_level { ProjectFeature::ENABLED }
|
||||
infrastructure_access_level { ProjectFeature::ENABLED }
|
||||
|
||||
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
|
||||
# `#ci_cd_settings` relation needs to be created first
|
||||
|
|
|
@ -205,6 +205,22 @@ RSpec.describe 'Admin updates settings' do
|
|||
expect(page).to have_content "Application settings saved successfully"
|
||||
end
|
||||
end
|
||||
|
||||
context 'Email confirmation settings' do
|
||||
it "is set to 'hard' by default" do
|
||||
expect(current_settings.email_confirmation_setting).to eq('hard')
|
||||
end
|
||||
|
||||
it 'changes the setting', :js do
|
||||
page.within('.as-signup') do
|
||||
choose 'Off'
|
||||
click_button 'Save changes'
|
||||
end
|
||||
|
||||
expect(current_settings.email_confirmation_setting).to eq('off')
|
||||
expect(page).to have_content "Application settings saved successfully"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'change Sign-in restrictions' do
|
||||
|
|
|
@ -4,6 +4,7 @@ export const rawMockData = {
|
|||
signupEnabled: 'true',
|
||||
requireAdminApprovalAfterUserSignup: 'true',
|
||||
sendUserConfirmationEmail: 'true',
|
||||
emailConfirmationSetting: 'hard',
|
||||
minimumPasswordLength: '8',
|
||||
minimumPasswordLengthMin: '3',
|
||||
minimumPasswordLengthMax: '10',
|
||||
|
@ -30,6 +31,7 @@ export const mockData = {
|
|||
signupEnabled: true,
|
||||
requireAdminApprovalAfterUserSignup: true,
|
||||
sendUserConfirmationEmail: true,
|
||||
emailConfirmationSetting: 'hard',
|
||||
minimumPasswordLength: '8',
|
||||
minimumPasswordLengthMin: '3',
|
||||
minimumPasswordLengthMax: '10',
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::Migrations::Runner, :reestablished_active_record_base do
|
||||
include Database::MultipleDatabases
|
||||
|
||||
let(:base_result_dir) { Pathname.new(Dir.mktmpdir) }
|
||||
|
||||
let(:migration_runs) { [] } # This list gets populated as the runner tries to run migrations
|
||||
|
|
|
@ -592,6 +592,7 @@ ProjectFeature:
|
|||
- feature_flags_access_level
|
||||
- releases_access_level
|
||||
- monitor_access_level
|
||||
- infrastructure_access_level
|
||||
- created_at
|
||||
- updated_at
|
||||
ProtectedBranch::MergeAccessLevel:
|
||||
|
|
|
@ -23,6 +23,52 @@ RSpec.describe Sidebars::Projects::Menus::InfrastructureMenu do
|
|||
expect(subject.render?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'behavior based on access level setting' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let(:enabled) { Featurable::PRIVATE }
|
||||
let(:disabled) { Featurable::DISABLED }
|
||||
|
||||
where(:operations_access_level, :infrastructure_access_level, :render) do
|
||||
ref(:disabled) | ref(:enabled) | true
|
||||
ref(:disabled) | ref(:disabled) | false
|
||||
ref(:enabled) | ref(:enabled) | true
|
||||
ref(:enabled) | ref(:disabled) | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'renders based on the infrastructure access level' do
|
||||
project.project_feature.update!(operations_access_level: operations_access_level)
|
||||
project.project_feature.update!(infrastructure_access_level: infrastructure_access_level)
|
||||
|
||||
expect(subject.render?).to be render
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `split_operations_visibility_permissions` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(split_operations_visibility_permissions: false)
|
||||
end
|
||||
|
||||
where(:operations_access_level, :infrastructure_access_level, :render) do
|
||||
ref(:disabled) | ref(:enabled) | false
|
||||
ref(:disabled) | ref(:disabled) | false
|
||||
ref(:enabled) | ref(:enabled) | true
|
||||
ref(:enabled) | ref(:disabled) | true
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'renders based on the operations access level' do
|
||||
project.project_feature.update!(operations_access_level: operations_access_level)
|
||||
project.project_feature.update!(infrastructure_access_level: infrastructure_access_level)
|
||||
|
||||
expect(subject.render?).to be render
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#link' do
|
||||
|
|
|
@ -8,7 +8,7 @@ RSpec.describe ProjectFeaturesCompatibility do
|
|||
let(:features) do
|
||||
features_enabled + %w(
|
||||
repository pages operations container_registry package_registry environments feature_flags releases
|
||||
monitor
|
||||
monitor infrastructure
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -862,6 +862,7 @@ RSpec.describe Project, factory_default: :keep do
|
|||
it { is_expected.to delegate_method(:environments_access_level).to(:project_feature) }
|
||||
it { is_expected.to delegate_method(:feature_flags_access_level).to(:project_feature) }
|
||||
it { is_expected.to delegate_method(:releases_access_level).to(:project_feature) }
|
||||
it { is_expected.to delegate_method(:infrastructure_access_level).to(:project_feature) }
|
||||
it { is_expected.to delegate_method(:maven_package_requests_forwarding).to(:namespace) }
|
||||
it { is_expected.to delegate_method(:pypi_package_requests_forwarding).to(:namespace) }
|
||||
it { is_expected.to delegate_method(:npm_package_requests_forwarding).to(:namespace) }
|
||||
|
|
|
@ -69,7 +69,8 @@ RSpec.describe WorkItems::Type do
|
|||
::WorkItems::Widgets::Hierarchy,
|
||||
::WorkItems::Widgets::Labels,
|
||||
::WorkItems::Widgets::Assignees,
|
||||
::WorkItems::Widgets::StartAndDueDate
|
||||
::WorkItems::Widgets::StartAndDueDate,
|
||||
::WorkItems::Widgets::Milestone
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
||||
describe 'infrastructure feature' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:guest_permissions) { [] }
|
||||
|
||||
let(:developer_permissions) do
|
||||
guest_permissions + [:read_terraform_state, :read_pod_logs, :read_prometheus]
|
||||
end
|
||||
|
||||
let(:maintainer_permissions) do
|
||||
developer_permissions + [:create_cluster, :read_cluster, :update_cluster, :admin_cluster, :admin_terraform_state, :admin_project_google_cloud]
|
||||
end
|
||||
|
||||
where(:project_visibility, :access_level, :role, :allowed) do
|
||||
:public | ProjectFeature::ENABLED | :maintainer | true
|
||||
:public | ProjectFeature::ENABLED | :developer | true
|
||||
:public | ProjectFeature::ENABLED | :guest | true
|
||||
:public | ProjectFeature::ENABLED | :anonymous | true
|
||||
:public | ProjectFeature::PRIVATE | :maintainer | true
|
||||
:public | ProjectFeature::PRIVATE | :developer | true
|
||||
:public | ProjectFeature::PRIVATE | :guest | true
|
||||
:public | ProjectFeature::PRIVATE | :anonymous | false
|
||||
:public | ProjectFeature::DISABLED | :maintainer | false
|
||||
:public | ProjectFeature::DISABLED | :developer | false
|
||||
:public | ProjectFeature::DISABLED | :guest | false
|
||||
:public | ProjectFeature::DISABLED | :anonymous | false
|
||||
:internal | ProjectFeature::ENABLED | :maintainer | true
|
||||
:internal | ProjectFeature::ENABLED | :developer | true
|
||||
:internal | ProjectFeature::ENABLED | :guest | true
|
||||
:internal | ProjectFeature::ENABLED | :anonymous | false
|
||||
:internal | ProjectFeature::PRIVATE | :maintainer | true
|
||||
:internal | ProjectFeature::PRIVATE | :developer | true
|
||||
:internal | ProjectFeature::PRIVATE | :guest | true
|
||||
:internal | ProjectFeature::PRIVATE | :anonymous | false
|
||||
:internal | ProjectFeature::DISABLED | :maintainer | false
|
||||
:internal | ProjectFeature::DISABLED | :developer | false
|
||||
:internal | ProjectFeature::DISABLED | :guest | false
|
||||
:internal | ProjectFeature::DISABLED | :anonymous | false
|
||||
:private | ProjectFeature::ENABLED | :maintainer | true
|
||||
:private | ProjectFeature::ENABLED | :developer | true
|
||||
:private | ProjectFeature::ENABLED | :guest | true
|
||||
:private | ProjectFeature::ENABLED | :anonymous | false
|
||||
:private | ProjectFeature::PRIVATE | :maintainer | true
|
||||
:private | ProjectFeature::PRIVATE | :developer | true
|
||||
:private | ProjectFeature::PRIVATE | :guest | true
|
||||
:private | ProjectFeature::PRIVATE | :anonymous | false
|
||||
:private | ProjectFeature::DISABLED | :maintainer | false
|
||||
:private | ProjectFeature::DISABLED | :developer | false
|
||||
:private | ProjectFeature::DISABLED | :guest | false
|
||||
:private | ProjectFeature::DISABLED | :anonymous | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:current_user) { user_subject(role) }
|
||||
let(:project) { project_subject(project_visibility) }
|
||||
|
||||
it 'allows/disallows the abilities based on the infrastructure access level' do
|
||||
project.project_feature.update!(infrastructure_access_level: access_level)
|
||||
|
||||
if allowed
|
||||
expect_allowed(*permissions_abilities(role))
|
||||
else
|
||||
expect_disallowed(*permissions_abilities(role))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'access_security_and_compliance' do
|
||||
context 'when the "Security & Compliance" is enabled' do
|
||||
before do
|
||||
|
|
|
@ -154,6 +154,68 @@ RSpec.describe 'Create a work item' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with milestone widget input' do
|
||||
let(:widgets_response) { mutation_response['workItem']['widgets'] }
|
||||
let(:fields) do
|
||||
<<~FIELDS
|
||||
workItem {
|
||||
widgets {
|
||||
type
|
||||
... on WorkItemWidgetMilestone {
|
||||
milestone {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
errors
|
||||
FIELDS
|
||||
end
|
||||
|
||||
let(:mutation) { graphql_mutation(:workItemCreate, input.merge('projectPath' => project.full_path), fields) }
|
||||
|
||||
context 'when setting milestone on work item creation' do
|
||||
let_it_be(:project_milestone) { create(:milestone, project: project) }
|
||||
let_it_be(:group_milestone) { create(:milestone, project: project) }
|
||||
|
||||
let(:input) do
|
||||
{
|
||||
title: 'some WI',
|
||||
workItemTypeId: WorkItems::Type.default_by_type(:task).to_global_id.to_s,
|
||||
milestoneWidget: { 'milestoneId' => milestone.to_global_id.to_s }
|
||||
}
|
||||
end
|
||||
|
||||
shared_examples "work item's milestone is set" do
|
||||
it "sets the work item's milestone" do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.to change(WorkItem, :count).by(1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(widgets_response).to include(
|
||||
{
|
||||
'type' => 'MILESTONE',
|
||||
'milestone' => { 'id' => milestone.to_global_id.to_s }
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assigning a project milestone' do
|
||||
it_behaves_like "work item's milestone is set" do
|
||||
let(:milestone) { project_milestone }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assigning a group milestone' do
|
||||
it_behaves_like "work item's milestone is set" do
|
||||
let(:milestone) { group_milestone }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the work_items feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(work_items: false)
|
||||
|
|
|
@ -5,8 +5,11 @@ require 'spec_helper'
|
|||
RSpec.describe 'Update a work item' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
|
||||
let_it_be(:reporter) { create(:user).tap { |user| project.add_reporter(user) } }
|
||||
let_it_be(:guest) { create(:user).tap { |user| project.add_guest(user) } }
|
||||
let_it_be(:work_item, refind: true) { create(:work_item, project: project) }
|
||||
|
||||
let(:work_item_event) { 'CLOSE' }
|
||||
|
@ -543,6 +546,91 @@ RSpec.describe 'Update a work item' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when updating milestone' do
|
||||
let_it_be(:project_milestone) { create(:milestone, project: project) }
|
||||
let_it_be(:group_milestone) { create(:milestone, project: project) }
|
||||
|
||||
let(:input) { { 'milestoneWidget' => { 'milestoneId' => new_milestone&.to_global_id&.to_s } } }
|
||||
|
||||
let(:fields) do
|
||||
<<~FIELDS
|
||||
workItem {
|
||||
widgets {
|
||||
type
|
||||
... on WorkItemWidgetMilestone {
|
||||
milestone {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
errors
|
||||
FIELDS
|
||||
end
|
||||
|
||||
shared_examples "work item's milestone is updated" do
|
||||
it "updates the work item's milestone" do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
work_item.reload
|
||||
end.to change(work_item, :milestone).from(old_milestone).to(new_milestone)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "work item's milestone is not updated" do
|
||||
it "ignores the update request" do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
work_item.reload
|
||||
end.to not_change(work_item, :milestone)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user cannot set work item metadata' do
|
||||
let(:current_user) { guest }
|
||||
let(:old_milestone) { nil }
|
||||
|
||||
it_behaves_like "work item's milestone is not updated" do
|
||||
let(:new_milestone) { project_milestone }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can set work item metadata' do
|
||||
let(:current_user) { reporter }
|
||||
|
||||
context 'when assigning a project milestone' do
|
||||
it_behaves_like "work item's milestone is updated" do
|
||||
let(:old_milestone) { nil }
|
||||
let(:new_milestone) { project_milestone }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assigning a group milestone' do
|
||||
it_behaves_like "work item's milestone is updated" do
|
||||
let(:old_milestone) { nil }
|
||||
let(:new_milestone) { group_milestone }
|
||||
end
|
||||
end
|
||||
|
||||
context "when unsetting the work item's milestone" do
|
||||
it_behaves_like "work item's milestone is updated" do
|
||||
let(:old_milestone) { group_milestone }
|
||||
let(:new_milestone) { nil }
|
||||
|
||||
before do
|
||||
work_item.update!(milestone: old_milestone)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unsupported widget input is sent' do
|
||||
let_it_be(:test_case) { create(:work_item_type, :default, :test_case, name: 'some_test_case_name') }
|
||||
let_it_be(:work_item) { create(:work_item, work_item_type: test_case, project: project) }
|
||||
|
|
|
@ -10,6 +10,8 @@ RSpec.describe 'getting a work item list for a project' do
|
|||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:label1) { create(:label, project: project) }
|
||||
let_it_be(:label2) { create(:label, project: project) }
|
||||
let_it_be(:milestone1) { create(:milestone, project: project) }
|
||||
let_it_be(:milestone2) { create(:milestone, project: project) }
|
||||
|
||||
let_it_be(:item1) { create(:work_item, project: project, discussion_locked: true, title: 'item1', labels: [label1]) }
|
||||
let_it_be(:item2) do
|
||||
|
@ -19,7 +21,8 @@ RSpec.describe 'getting a work item list for a project' do
|
|||
title: 'item2',
|
||||
last_edited_by: current_user,
|
||||
last_edited_at: 1.day.ago,
|
||||
labels: [label2]
|
||||
labels: [label2],
|
||||
milestone: milestone1
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -55,7 +58,8 @@ RSpec.describe 'getting a work item list for a project' do
|
|||
:last_edited_by_user,
|
||||
last_edited_at: 1.week.ago,
|
||||
project: project,
|
||||
labels: [label1, label2]
|
||||
labels: [label1, label2],
|
||||
milestone: milestone2
|
||||
)
|
||||
|
||||
expect_graphql_errors_to_be_empty
|
||||
|
@ -94,6 +98,11 @@ RSpec.describe 'getting a work item list for a project' do
|
|||
labels { nodes { id } }
|
||||
allowsScopedLabels
|
||||
}
|
||||
... on WorkItemWidgetMilestone {
|
||||
milestone {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GRAPHQL
|
||||
|
|
|
@ -298,6 +298,40 @@ RSpec.describe 'Query.work_item(id)' do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'milestone widget' do
|
||||
let_it_be(:milestone) { create(:milestone, project: project) }
|
||||
|
||||
let(:work_item) { create(:work_item, project: project, milestone: milestone) }
|
||||
|
||||
let(:work_item_fields) do
|
||||
<<~GRAPHQL
|
||||
id
|
||||
widgets {
|
||||
type
|
||||
... on WorkItemWidgetMilestone {
|
||||
milestone {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
GRAPHQL
|
||||
end
|
||||
|
||||
it 'returns widget information' do
|
||||
expect(work_item_data).to include(
|
||||
'id' => work_item.to_gid.to_s,
|
||||
'widgets' => include(
|
||||
hash_including(
|
||||
'type' => 'MILESTONE',
|
||||
'milestone' => {
|
||||
'id' => work_item.milestone.to_gid.to_s
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an Issue Global ID is provided' do
|
||||
|
|
|
@ -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 TestEnv
|
||||
config.include FileReadHelpers
|
||||
config.include Database::MultipleDatabases
|
||||
config.include Database::MultipleDatabasesHelpers
|
||||
config.include Database::WithoutCheckConstraint
|
||||
config.include Devise::Test::ControllerHelpers, type: :controller
|
||||
config.include Devise::Test::ControllerHelpers, type: :view
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Database
|
||||
module MultipleDatabases
|
||||
module MultipleDatabasesHelpers
|
||||
def skip_if_multiple_databases_not_setup
|
||||
skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
|
||||
end
|
||||
|
@ -52,17 +52,17 @@ module Database
|
|||
#
|
||||
# rubocop:disable Database/MultipleDatabases
|
||||
def with_reestablished_active_record_base(reconnect: true)
|
||||
connection_classes = ActiveRecord::Base.connection_handler.connection_pool_names.map(&:constantize).to_h do |klass|
|
||||
[klass, klass.connection_db_config]
|
||||
end
|
||||
connection_classes = ActiveRecord::Base
|
||||
.connection_handler
|
||||
.connection_pool_names
|
||||
.map(&:constantize)
|
||||
.index_with(&:connection_db_config)
|
||||
|
||||
original_handler = ActiveRecord::Base.connection_handler
|
||||
new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
|
||||
ActiveRecord::Base.connection_handler = new_handler
|
||||
|
||||
if reconnect
|
||||
connection_classes.each { |klass, db_config| klass.establish_connection(db_config) }
|
||||
end
|
||||
connection_classes.each { |klass, db_config| klass.establish_connection(db_config) } if reconnect
|
||||
|
||||
yield
|
||||
ensure
|
||||
|
@ -95,9 +95,12 @@ module Database
|
|||
module ActiveRecordBaseEstablishConnection
|
||||
def establish_connection(*args)
|
||||
# rubocop:disable Database/MultipleDatabases
|
||||
if connected? && connection&.transaction_open? && ActiveRecord::Base.connection_handler == ActiveRecord::Base.default_connection_handler
|
||||
raise "Cannot re-establish '#{self}.establish_connection' within an open transaction (#{connection&.open_transactions.to_i}). " \
|
||||
"Use `with_reestablished_active_record_base` instead or add `:reestablished_active_record_base` to rspec context."
|
||||
if connected? &&
|
||||
connection&.transaction_open? &&
|
||||
ActiveRecord::Base.connection_handler == ActiveRecord::Base.default_connection_handler
|
||||
raise "Cannot re-establish '#{self}.establish_connection' within an open transaction " \
|
||||
"(#{connection&.open_transactions.to_i}). Use `with_reestablished_active_record_base` " \
|
||||
"instead or add `:reestablished_active_record_base` to rspec context."
|
||||
end
|
||||
# rubocop:enable Database/MultipleDatabases
|
||||
|
||||
|
@ -106,56 +109,4 @@ module Database
|
|||
end
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
# Ensure database versions are memoized to prevent query counts from
|
||||
# being affected by version checks. Note that
|
||||
# Gitlab::Database.check_postgres_version_and_print_warning is called
|
||||
# at startup, but that generates its own
|
||||
# `Gitlab::Database::Reflection` so the result is not memoized by
|
||||
# callers of `ApplicationRecord.database.version`, such as
|
||||
# `Gitlab::Database::AsWithMaterialized.materialized_supported?`.
|
||||
# TODO This can be removed once https://gitlab.com/gitlab-org/gitlab/-/issues/325639 is completed.
|
||||
[ApplicationRecord, ::Ci::ApplicationRecord].each { |record| record.database.version }
|
||||
|
||||
config.around(:each, :reestablished_active_record_base) do |example|
|
||||
with_reestablished_active_record_base(reconnect: example.metadata.fetch(:reconnect, true)) do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
config.around(:each, :add_ci_connection) do |example|
|
||||
with_added_ci_connection do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
config.append_after(:context, :migration) do
|
||||
recreate_databases_and_seed_if_needed || ensure_schema_and_empty_tables
|
||||
end
|
||||
|
||||
config.around(:each, :migration) do |example|
|
||||
self.class.use_transactional_tests = false
|
||||
|
||||
migration_schema = example.metadata[:migration]
|
||||
migration_schema = :gitlab_main if migration_schema == true
|
||||
base_model = Gitlab::Database.schemas_to_base_models.fetch(migration_schema).first
|
||||
|
||||
# Migration require an `ActiveRecord::Base` to point to desired database
|
||||
if base_model != ActiveRecord::Base
|
||||
with_reestablished_active_record_base do
|
||||
reconfigure_db_connection(
|
||||
model: ActiveRecord::Base,
|
||||
config_model: base_model
|
||||
)
|
||||
|
||||
example.run
|
||||
end
|
||||
else
|
||||
example.run
|
||||
end
|
||||
|
||||
self.class.use_transactional_tests = true
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecord::Base.singleton_class.prepend(::Database::ActiveRecordBaseEstablishConnection) # rubocop:disable Database/MultipleDatabases
|
|
@ -16,14 +16,42 @@ RSpec.configure do |config|
|
|||
schema_migrate_down!
|
||||
end
|
||||
|
||||
config.after(:context, :migration) do
|
||||
Gitlab::CurrentSettings.clear_in_memory_application_settings!
|
||||
end
|
||||
|
||||
config.append_after(:context, :migration) do
|
||||
recreate_databases_and_seed_if_needed || ensure_schema_and_empty_tables
|
||||
end
|
||||
|
||||
config.around(:each, :migration) do |example|
|
||||
self.class.use_transactional_tests = false
|
||||
|
||||
migration_schema = example.metadata[:migration]
|
||||
migration_schema = :gitlab_main if migration_schema == true
|
||||
base_model = Gitlab::Database.schemas_to_base_models.fetch(migration_schema).first
|
||||
|
||||
# Migration require an `ActiveRecord::Base` to point to desired database
|
||||
if base_model != ActiveRecord::Base
|
||||
with_reestablished_active_record_base do
|
||||
reconfigure_db_connection(
|
||||
model: ActiveRecord::Base,
|
||||
config_model: base_model
|
||||
)
|
||||
|
||||
example.run
|
||||
end
|
||||
else
|
||||
example.run
|
||||
end
|
||||
|
||||
self.class.use_transactional_tests = true
|
||||
end
|
||||
|
||||
# Each example may call `migrate!`, so we must ensure we are migrated down every time
|
||||
config.before(:each, :migration) do
|
||||
use_fake_application_settings
|
||||
|
||||
schema_migrate_down!
|
||||
end
|
||||
|
||||
config.after(:context, :migration) do
|
||||
Gitlab::CurrentSettings.clear_in_memory_application_settings!
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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 }
|
||||
|
||||
before do
|
||||
subject.create_page('index', 'This is an index')
|
||||
# The order is intentional
|
||||
subject.create_page('index2', 'This is an index2')
|
||||
subject.create_page('an index3', 'This is an index3')
|
||||
subject.create_page('index', 'This is an index')
|
||||
subject.create_page('index3', 'This is an index3')
|
||||
end
|
||||
|
||||
it 'returns an array of WikiPage instances' do
|
||||
|
@ -183,13 +184,47 @@ RSpec.shared_examples 'wiki model' do
|
|||
|
||||
context 'with limit option' do
|
||||
it 'returns limited set of pages' do
|
||||
expect(subject.list_pages(limit: 1).count).to eq(1)
|
||||
expect(
|
||||
subject.list_pages(limit: 1).map(&:title)
|
||||
).to eql(%w[index])
|
||||
end
|
||||
|
||||
it 'returns all set of pages if limit is more than the total pages' do
|
||||
expect(subject.list_pages(limit: 4).count).to eq(3)
|
||||
end
|
||||
|
||||
it 'returns all set of pages if limit is 0' do
|
||||
expect(subject.list_pages(limit: 0).count).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with offset option' do
|
||||
it 'returns offset-ed set of pages' do
|
||||
expect(
|
||||
subject.list_pages(offset: 1).map(&:title)
|
||||
).to eq(%w[index2 index3])
|
||||
|
||||
expect(
|
||||
subject.list_pages(offset: 2).map(&:title)
|
||||
).to eq(["index3"])
|
||||
expect(subject.list_pages(offset: 3).count).to eq(0)
|
||||
expect(subject.list_pages(offset: 4).count).to eq(0)
|
||||
end
|
||||
|
||||
it 'returns all set of pages if offset is 0' do
|
||||
expect(subject.list_pages(offset: 0).count).to eq(3)
|
||||
end
|
||||
|
||||
it 'can combines with limit' do
|
||||
expect(
|
||||
subject.list_pages(offset: 1, limit: 1).map(&:title)
|
||||
).to eq(["index2"])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with sorting options' do
|
||||
it 'returns pages sorted by title by default' do
|
||||
pages = ['an index3', 'index', 'index2']
|
||||
pages = %w[index index2 index3]
|
||||
|
||||
expect(subject.list_pages.map(&:title)).to eq(pages)
|
||||
expect(subject.list_pages(direction: 'desc').map(&:title)).to eq(pages.reverse)
|
||||
|
@ -200,9 +235,9 @@ RSpec.shared_examples 'wiki model' do
|
|||
let(:pages) { subject.list_pages(load_content: true) }
|
||||
|
||||
it 'loads WikiPage content' do
|
||||
expect(pages.first.content).to eq('This is an index3')
|
||||
expect(pages.second.content).to eq('This is an index')
|
||||
expect(pages.third.content).to eq('This is an index2')
|
||||
expect(pages.first.content).to eq('This is an index')
|
||||
expect(pages.second.content).to eq('This is an index2')
|
||||
expect(pages.third.content).to eq('This is an index3')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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'
|
||||
|
||||
RSpec.describe 'Database::MultipleDatabases' do
|
||||
RSpec.describe 'Database::MultipleDatabasesHelpers' do
|
||||
let(:query) do
|
||||
<<~SQL
|
||||
WITH cte AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (SELECT 1) SELECT 1;
|
||||
|
@ -72,7 +72,8 @@ RSpec.describe 'Database::MultipleDatabases' do
|
|||
context 'when reconnect is false' do
|
||||
it 'does raise exception' do
|
||||
with_reestablished_active_record_base(reconnect: false) do
|
||||
expect { ApplicationRecord.connection.execute("SELECT 1") }.to raise_error(ActiveRecord::ConnectionNotEstablished)
|
||||
expect { ApplicationRecord.connection.execute("SELECT 1") }
|
||||
.to raise_error(ActiveRecord::ConnectionNotEstablished)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue