Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-05-26 09:08:11 +00:00
parent 864dae0d98
commit 026a5e9101
55 changed files with 440 additions and 806 deletions

View File

@ -1 +1 @@
ac989862106589558866e01fc5d77ad7326c99e4
a6c5964bb455f77a0c79898f0b99c4c7df3aee3a

View File

@ -191,7 +191,7 @@ export default {
<div class="col-md-12 col-lg-6">
<div class="gl-display-flex gl-flex-wrap gl-justify-content-end">
<gl-button v-if="admin" class="gl-mt-3" variant="info" @click="loadFileSelctor">
<gl-button v-if="admin" class="gl-mt-3" variant="confirm" @click="loadFileSelctor">
<span v-if="uploading">
<gl-loading-icon size="sm" class="gl-my-5" inline />
{{ $options.i18n.uploadingLabel }}

View File

@ -1,5 +1,3 @@
import { __ } from '~/locale';
export default class FilteredSearchTokenKeys {
constructor(tokenKeys = [], alternativeTokenKeys = [], conditions = []) {
this.tokenKeys = tokenKeys;
@ -76,24 +74,6 @@ export default class FilteredSearchTokenKeys {
);
}
addExtraTokensForIssues() {
const confidentialToken = {
formattedKey: __('Confidential'),
key: 'confidential',
type: 'string',
param: '',
symbol: '',
icon: 'eye-slash',
tag: __('Yes or No'),
lowercaseValueOnSubmit: true,
uppercaseTokenName: false,
capitalizeTokenValue: true,
};
this.tokenKeys.push(confidentialToken);
this.tokenKeysWithAlternative.push(confidentialToken);
}
removeTokensForKeys(...keys) {
const keysSet = new Set(keys);

View File

@ -129,5 +129,8 @@
"VulnerabilityLocationGeneric",
"VulnerabilityLocationSast",
"VulnerabilityLocationSecretDetection"
],
"WorkItemWidget": [
"WorkItemWidgetDescription"
]
}

View File

@ -1,26 +1,3 @@
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import { initBulkUpdateSidebar } from '~/issuable/bulk_update_sidebar';
import { mountIssuesListApp } from '~/issues/list';
import initManualOrdering from '~/issues/manual_ordering';
import { FILTERED_SEARCH } from '~/filtered_search/constants';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import projectSelect from '~/project_select';
if (gon.features?.vueIssuesList) {
mountIssuesListApp();
} else {
const ISSUE_BULK_UPDATE_PREFIX = 'issue_';
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
IssuableFilteredSearchTokenKeys.removeTokensForKeys('release');
initBulkUpdateSidebar(ISSUE_BULK_UPDATE_PREFIX);
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
isGroupDecendent: true,
useDefaultState: true,
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
projectSelect();
initManualOrdering();
}
mountIssuesListApp();

View File

@ -1,34 +1,6 @@
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import { initCsvImportExportButtons, initIssuableByEmail } from '~/issuable';
import { initBulkUpdateSidebar, initIssueStatusSelect } from '~/issuable/bulk_update_sidebar';
import { mountIssuesListApp, mountJiraIssuesListApp } from '~/issues/list';
import initManualOrdering from '~/issues/manual_ordering';
import { FILTERED_SEARCH } from '~/filtered_search/constants';
import { ISSUABLE_INDEX } from '~/issuable/constants';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import UsersSelect from '~/users_select';
if (gon.features?.vueIssuesList) {
mountIssuesListApp();
} else {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
useDefaultState: true,
});
initBulkUpdateSidebar(ISSUABLE_INDEX.ISSUE);
initIssueStatusSelect();
new UsersSelect(); // eslint-disable-line no-new
initCsvImportExportButtons();
initIssuableByEmail();
initManualOrdering();
}
new ShortcutsNavigation(); // eslint-disable-line no-new
mountIssuesListApp();
mountJiraIssuesListApp();
new ShortcutsNavigation(); // eslint-disable-line no-new

View File

@ -8,7 +8,7 @@ class Clusters::ClustersController < Clusters::BaseController
before_action :cluster, only: [:cluster_status, :show, :update, :destroy, :clear_cache]
before_action :user_cluster, only: [:connect]
before_action :authorize_read_cluster!, only: [:show, :index]
before_action :authorize_create_cluster!, only: [:connect, :authorize_aws_role]
before_action :authorize_create_cluster!, only: [:connect]
before_action :authorize_update_cluster!, only: [:update]
before_action :update_applications_status, only: [:cluster_status]
before_action :ensure_feature_enabled!, except: [:index, :new_cluster_docs]
@ -16,15 +16,6 @@ class Clusters::ClustersController < Clusters::BaseController
helper_method :token_in_session
STATUS_POLLING_INTERVAL = 10_000
AWS_CSP_DOMAINS = %w[https://ec2.ap-east-1.amazonaws.com https://ec2.ap-northeast-1.amazonaws.com https://ec2.ap-northeast-2.amazonaws.com https://ec2.ap-northeast-3.amazonaws.com https://ec2.ap-south-1.amazonaws.com https://ec2.ap-southeast-1.amazonaws.com https://ec2.ap-southeast-2.amazonaws.com https://ec2.ca-central-1.amazonaws.com https://ec2.eu-central-1.amazonaws.com https://ec2.eu-north-1.amazonaws.com https://ec2.eu-west-1.amazonaws.com https://ec2.eu-west-2.amazonaws.com https://ec2.eu-west-3.amazonaws.com https://ec2.me-south-1.amazonaws.com https://ec2.sa-east-1.amazonaws.com https://ec2.us-east-1.amazonaws.com https://ec2.us-east-2.amazonaws.com https://ec2.us-west-1.amazonaws.com https://ec2.us-west-2.amazonaws.com https://ec2.af-south-1.amazonaws.com https://iam.amazonaws.com].freeze
content_security_policy do |p|
next if p.directives.blank?
default_connect_src = p.directives['connect-src'] || p.directives['default-src']
connect_src_values = Array.wrap(default_connect_src) | AWS_CSP_DOMAINS
p.connect_src(*connect_src_values)
end
def index
@clusters = cluster_list
@ -95,19 +86,6 @@ class Clusters::ClustersController < Clusters::BaseController
redirect_to clusterable.index_path, status: :found
end
def create_aws
@aws_cluster = ::Clusters::CreateService
.new(current_user, create_aws_cluster_params)
.execute
.present(current_user: current_user)
if @aws_cluster.persisted?
head :created, location: @aws_cluster.show_path
else
render status: :unprocessable_entity, json: @aws_cluster.errors
end
end
def create_user
@user_cluster = ::Clusters::CreateService
.new(current_user, create_user_cluster_params)
@ -117,23 +95,10 @@ class Clusters::ClustersController < Clusters::BaseController
if @user_cluster.persisted?
redirect_to @user_cluster.show_path
else
generate_gcp_authorize_url
validate_gcp_token
gcp_cluster
render :connect
end
end
def authorize_aws_role
response = Clusters::Aws::AuthorizeRoleService.new(
current_user,
params: aws_role_params
).execute
render json: response.body, status: response.status
end
def clear_cache
cluster.delete_cached_resources!
@ -204,27 +169,6 @@ class Clusters::ClustersController < Clusters::BaseController
end
end
def create_aws_cluster_params
params.require(:cluster).permit(
*base_permitted_cluster_params,
:name,
provider_aws_attributes: [
:kubernetes_version,
:key_name,
:role_arn,
:region,
:vpc_id,
:instance_type,
:num_nodes,
:security_group_id,
subnet_ids: []
]).merge(
provider_type: :aws,
platform_type: :kubernetes,
clusterable: clusterable.__subject__
)
end
def create_user_cluster_params
params.require(:cluster).permit(
*base_permitted_cluster_params,
@ -242,29 +186,6 @@ class Clusters::ClustersController < Clusters::BaseController
)
end
def aws_role_params
params.require(:cluster).permit(:role_arn, :region)
end
def generate_gcp_authorize_url
connect_path = clusterable.connect_path().to_s
error_path = @project ? project_clusters_path(@project) : connect_path
state = generate_session_key_redirect(connect_path, error_path)
@authorize_url = GoogleApi::CloudPlatform::Client.new(
nil, callback_google_api_auth_url,
state: state).authorize_url
rescue GoogleApi::Auth::ConfigMissingError
# no-op
end
def gcp_cluster
cluster = Clusters::BuildService.new(clusterable.__subject__).execute
cluster.build_provider_gcp
@gcp_cluster = cluster.present(current_user: current_user)
end
def proxyable
cluster.cluster
end
@ -295,11 +216,6 @@ class Clusters::ClustersController < Clusters::BaseController
@user_cluster = cluster.present(current_user: current_user)
end
def validate_gcp_token
@valid_gcp_token = GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
.validate_token(expires_at_in_session)
end
def token_in_session
session[GoogleApi::CloudPlatform::Client.session_key_for_token]
end
@ -309,26 +225,6 @@ class Clusters::ClustersController < Clusters::BaseController
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]
end
def generate_session_key_redirect(uri, error_uri)
GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key|
session[key] = uri
session[:error_uri] = error_uri
end
end
##
# Unfortunately the EC2 API doesn't provide a list of
# possible instance types. There is a workaround, using
# the Pricing API, but instead of requiring the
# user to grant extra permissions for this we use the
# values that validate the CloudFormation template.
def load_instance_types
stack_template = File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'))
instance_types = YAML.safe_load(stack_template).dig('Parameters', 'NodeInstanceType', 'AllowedValues')
instance_types.map { |type| Hash(name: type, value: type) }
end
def update_applications_status
@cluster.applications.each(&:schedule_status_update)
end

View File

@ -30,10 +30,6 @@ class GroupsController < Groups::ApplicationController
before_action :user_actions, only: [:show]
before_action do
push_frontend_feature_flag(:vue_issues_list, @group)
end
before_action :check_export_rate_limit!, only: [:export, :download_export]
before_action :track_experiment_event, only: [:new]
@ -212,7 +208,7 @@ class GroupsController < Groups::ApplicationController
end
def issues
return super if !html_request? || Feature.disabled?(:vue_issues_list, group)
return super unless html_request?
@has_issues = IssuesFinder.new(current_user, group_id: group.id, include_subgroups: true).execute
.non_archived

View File

@ -84,7 +84,7 @@ class Projects::CommitsController < Projects::ApplicationController
@commits.each(&:lazy_author) # preload authors
@commits = @commits.with_latest_pipeline(@ref)
@commits = @commits.with_markdown_cache.with_latest_pipeline(@ref)
@commits = set_commits_for_rendering(@commits)
end

View File

@ -23,7 +23,7 @@ class Projects::IssuesController < Projects::ApplicationController
after_action :log_issue_show, unless: ->(c) { ISSUES_EXCEPT_ACTIONS.include?(c.action_name.to_sym) }
before_action :set_issuables_index, if: ->(c) {
SET_ISSUABLES_INDEX_ONLY_ACTIONS.include?(c.action_name.to_sym) && !vue_issues_list?
SET_ISSUABLES_INDEX_ONLY_ACTIONS.include?(c.action_name.to_sym) && !index_html_request?
}
# Allow write(create) issue
@ -39,7 +39,6 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_download_code!, only: [:related_branches]
before_action do
push_frontend_feature_flag(:vue_issues_list, project&.group)
push_frontend_feature_flag(:contacts_autocomplete, project&.group)
push_frontend_feature_flag(:incident_timeline, project)
end
@ -82,7 +81,7 @@ class Projects::IssuesController < Projects::ApplicationController
attr_accessor :vulnerability_id
def index
if vue_issues_list?
if index_html_request?
set_sort_order
else
@issues = @issuables
@ -258,10 +257,8 @@ class Projects::IssuesController < Projects::ApplicationController
protected
def vue_issues_list?
action_name.to_sym == :index &&
html_request? &&
Feature.enabled?(:vue_issues_list, project&.group)
def index_html_request?
action_name.to_sym == :index && html_request?
end
def sorting_field

View File

@ -27,14 +27,7 @@ module Projects
).to_json
end
if current_user.ci_owned_runners_cross_joins_fix_enabled?
render
else
# @assignable_runners is using ci_owned_runners
::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') do
render
end
end
render
end
def update

View File

@ -18,6 +18,8 @@ module Types
description: 'State of the work item.'
field :title, GraphQL::Types::String, null: false,
description: 'Title of the work item.'
field :widgets, [Types::WorkItems::WidgetInterface], null: true,
description: 'Collection of widgets that belong to the work item.'
field :work_item_type, Types::WorkItems::TypeType, null: false,
description: 'Type assigned to the work item.'

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Types
module WorkItems
module WidgetInterface
include Types::BaseInterface
graphql_name 'WorkItemWidget'
field :type, ::Types::WorkItems::WidgetTypeEnum, null: true,
description: 'Widget type.'
def self.resolve_type(object, context)
case object
when ::WorkItems::Widgets::Description
::Types::WorkItems::Widgets::DescriptionType
else
raise "Unknown GraphQL type for widget #{object}"
end
end
orphan_types ::Types::WorkItems::Widgets::DescriptionType
end
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Types
module WorkItems
class WidgetTypeEnum < BaseEnum
graphql_name 'WorkItemWidgetType'
description 'Type of a work item widget'
::WorkItems::Type.available_widgets.each do |widget|
value widget.type.to_s.upcase, value: widget.type, description: "#{widget.type.to_s.titleize} widget."
end
end
end
end

View File

@ -0,0 +1,25 @@
# 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 DescriptionType < BaseObject
graphql_name 'WorkItemWidgetDescription'
description 'Represents a description widget'
implements Types::WorkItems::WidgetInterface
field :description, GraphQL::Types::String, null: true,
description: 'Description of the work item.'
markdown_field :description_html, null: true do |resolved_object|
resolved_object.work_item
end
end
# rubocop:enable Graphql/AuthorizeTypes
end
end
end

View File

@ -1650,33 +1650,15 @@ class User < ApplicationRecord
def ci_owned_runners
@ci_owned_runners ||= begin
if ci_owned_runners_cross_joins_fix_enabled?
Ci::Runner
.from_union([ci_owned_project_runners_from_project_members,
ci_owned_project_runners_from_group_members,
ci_owned_group_runners])
else
Ci::Runner
.from_union([ci_legacy_owned_project_runners, ci_legacy_owned_group_runners])
.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436')
end
Ci::Runner
.from_union([ci_owned_project_runners_from_project_members,
ci_owned_project_runners_from_group_members,
ci_owned_group_runners])
end
end
def owns_runner?(runner)
if ci_owned_runners_cross_joins_fix_enabled?
ci_owned_runners.exists?(runner.id)
else
::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') do
ci_owned_runners.exists?(runner.id)
end
end
end
def ci_owned_runners_cross_joins_fix_enabled?
strong_memoize(:ci_owned_runners_cross_joins_fix_enabled) do
Feature.enabled?(:ci_owned_runners_cross_joins_fix, self)
end
ci_owned_runners.exists?(runner.id)
end
def notification_email_for(notification_group)
@ -2258,20 +2240,6 @@ class User < ApplicationRecord
::Gitlab::Auth::Ldap::Access.allowed?(self)
end
def ci_legacy_owned_project_runners
Ci::RunnerProject
.select('ci_runners.*')
.joins(:runner)
.where(project: authorized_projects(Gitlab::Access::MAINTAINER))
end
def ci_legacy_owned_group_runners
Ci::RunnerNamespace
.select('ci_runners.*')
.joins(:runner)
.where(namespace_id: owned_groups.self_and_descendant_ids)
end
def ci_owned_project_runners_from_project_members
project_ids = project_members.where('access_level >= ?', Gitlab::Access::MAINTAINER).pluck(:source_id)

View File

@ -15,6 +15,12 @@ class WorkItem < Issue
'issue'
end
def widgets
work_item_type.widgets.map do |widget_class|
widget_class.new(self)
end
end
private
def record_create_action

View File

@ -20,6 +20,14 @@ module WorkItems
task: { name: 'Task', icon_name: 'issue-type-task', enum_value: 4 }
}.freeze
WIDGETS_FOR_TYPE = {
issue: [Widgets::Description],
incident: [Widgets::Description],
test_case: [Widgets::Description],
requirement: [Widgets::Description],
task: [Widgets::Description]
}.freeze
cache_markdown_field :description, pipeline: :single_line
enum base_type: BASE_TYPES.transform_values { |value| value[:enum_value] }
@ -40,6 +48,10 @@ module WorkItems
scope :order_by_name_asc, -> { order(arel_table[:name].lower.asc) }
scope :by_type, ->(base_type) { where(base_type: base_type) }
def self.available_widgets
WIDGETS_FOR_TYPE.values.flatten.uniq
end
def self.default_by_type(type)
found_type = find_by(namespace_id: nil, base_type: type)
return found_type if found_type
@ -60,6 +72,10 @@ module WorkItems
namespace.blank?
end
def widgets
WIDGETS_FOR_TYPE[base_type.to_sym]
end
private
def strip_whitespace

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module WorkItems
module Widgets
class Base
def self.type
name.demodulize.underscore.to_sym
end
def type
self.class.type
end
def initialize(work_item)
@work_item = work_item
end
attr_reader :work_item
end
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
module WorkItems
module Widgets
class Description < Base
delegate :description, to: :work_item
end
end
end

View File

@ -1,28 +1,8 @@
- @can_bulk_update = can?(current_user, :admin_issue, @group) && @group.licensed_feature_available?(:group_bulk_edit)
- page_title _("Issues")
- page_title _('Issues')
- add_page_specific_style 'page_bundles/issues_list'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
- if Feature.enabled?(:vue_issues_list, @group)
.js-issues-list{ data: group_issues_list_data(@group, current_user) }
- if @can_bulk_update
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
- else
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
= render 'shared/issuable/feed_buttons'
- if @can_bulk_update
= render_if_exists 'shared/issuable/bulk_update_button', type: :issues
= render 'shared/new_project_item_select', path: 'issues/new', label: _("issue"), type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true
= render 'shared/issuable/search_bar', type: :issues
- if @can_bulk_update
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
= render 'shared/issues', project_select_button: true
.js-issues-list{ data: group_issues_list_data(@group, current_user) }
- if can?(current_user, :admin_issue, @group) && @group.licensed_feature_available?(:group_bulk_edit)
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues

View File

@ -1,10 +1,5 @@
- @can_bulk_update = can?(current_user, :admin_issue, @project)
- page_title _("Issues")
- new_issue_email = @project.new_issuable_address(current_user, 'issue')
- page_title _('Issues')
- add_page_specific_style 'page_bundles/issues_list'
- issuable_type = 'issue'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
@ -13,24 +8,6 @@
issues_path: project_issues_path(@project),
project_path: @project.full_path } }
- if Feature.enabled?(:vue_issues_list, @project&.group)
.js-issues-list{ data: project_issues_list_data(@project, current_user) }
- if @can_bulk_update
= render 'shared/issuable/bulk_update_sidebar', type: :issues
- elsif project_issues(@project).exists?
.top-area
= render 'shared/issuable/nav', type: :issues
= render "projects/issues/nav_btns"
= render 'shared/issuable/search_bar', type: :issues
- if @can_bulk_update
= render 'shared/issuable/bulk_update_sidebar', type: :issues
.issues-holder
= render 'issues'
- if new_issue_email
.gl-text-center.gl-pt-5.gl-pb-7
.js-issuable-by-email{ data: { initial_email: new_issue_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } }
- else
- new_project_issue_button_path = @project.archived? ? false : new_project_issue_path(@project)
= render 'shared/empty_states/issues', new_project_issue_button_path: new_project_issue_button_path, show_import_button: true
.js-issues-list{ data: project_issues_list_data(@project, current_user) }
- if can?(current_user, :admin_issue, @project)
= render 'shared/issuable/bulk_update_sidebar', type: :issues

View File

@ -1,8 +0,0 @@
---
name: ci_owned_runners_cross_joins_fix
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78216
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350322
milestone: '14.8'
type: development
group: group::pipeline execution
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: vue_issues_list
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55699
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323743
milestone: '13.10'
type: development
group: group::project management
default_enabled: true

View File

@ -247,8 +247,6 @@ Rails.application.routes.draw do
get :connect
get :new_cluster_docs
post :create_user
post :create_aws
post :authorize_aws_role
end
resource :integration, controller: 'clusters/integrations', only: [] do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
class CleanUpFixMergeRequestDiffCommitUsers < Gitlab::Database::Migration[1.0]
class CleanUpFixMergeRequestDiffCommitUsers < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
MIGRATION_CLASS = 'FixMergeRequestDiffCommitUsers'

View File

@ -53,8 +53,7 @@ Geo provides:
### Gitaly Cluster
Geo should not be confused with [Gitaly Cluster](../gitaly/praefect.md). For more information about
the difference between Geo and Gitaly Cluster, see
[How does Gitaly Cluster compare to Geo?](../gitaly/faq.md#how-does-gitaly-cluster-compare-to-geo).
the difference between Geo and Gitaly Cluster, see [Comparison to Geo](../gitaly/index.md#comparison-to-geo).
## How it works

View File

@ -1,90 +1,6 @@
---
stage: Create
group: Gitaly
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
redirect_to: 'index.md'
remove_date: '2022-08-24'
---
# Frequently asked questions **(FREE SELF)**
The following are answers to frequently asked questions about Gitaly and Gitaly Cluster. For
troubleshooting information, see [Troubleshooting Gitaly and Gitaly Cluster](troubleshooting.md).
## How does Gitaly Cluster compare to Geo?
Gitaly Cluster and [Geo](../geo/index.md) both provide redundancy. However the redundancy of:
- Gitaly Cluster provides fault tolerance for data storage and is invisible to the user. Users are
not aware when Gitaly Cluster is used.
- Geo provides [replication](../geo/index.md) and [disaster recovery](../geo/disaster_recovery/index.md) for
an entire instance of GitLab. Users know when they are using Geo for
[replication](../geo/index.md). Geo [replicates multiple data types](../geo/replication/datatypes.md#limitations-on-replicationverification),
including Git data.
The following table outlines the major differences between Gitaly Cluster and Geo:
| Tool | Nodes | Locations | Latency tolerance | Failover | Consistency | Provides redundancy for |
|:---------------|:---------|:----------|:------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------|:--------------------------------------|:------------------------|
| Gitaly Cluster | Multiple | Single | [Less than 1 second, ideally single-digit milliseconds](praefect.md#network-latency-and-connectivity) | [Automatic](praefect.md#automatic-failover-and-primary-election-strategies) | [Strong](index.md#strong-consistency) | Data storage in Git |
| Geo | Multiple | Multiple | Up to one minute | [Manual](../geo/disaster_recovery/index.md) | Eventual | Entire GitLab instance |
For more information, see:
- Geo [use cases](../geo/index.md#use-cases).
- Geo [architecture](../geo/index.md#architecture).
## Are there instructions for migrating to Gitaly Cluster?
Yes! For more information, see [Migrating to Gitaly Cluster](index.md#migrating-to-gitaly-cluster).
## What are some repository storage recommendations?
The size of the required storage can vary between instances and depends on the set
[replication factor](index.md#replication-factor). You might want to include implementing
repository storage redundancy.
For a replication factor:
- Of `1`: NFS, Gitaly, and Gitaly Cluster have roughly the same storage requirements.
- More than `1`: The amount of required storage is `used space * replication factor`. `used space`
should include any planned future growth.
## What are some Praefect database storage requirements?
The requirements are relatively low because the database contains only metadata of:
- Where repositories are located.
- Some queued work.
It depends on the number of repositories, but a useful minimum is 5-10 GB, similar to the main
GitLab application database.
## Can the GitLab application database and the Praefect database be on the same servers?
Yes, however Praefect should have it's own database server when using Omnibus GitLab PostgreSQL. If
there is a failover, Praefect isn't aware and starts to fail as the database it's trying to use would
either:
- Be unavailable.
- In read-only mode.
A future solution may allow for Praefect and Omnibus GitLab databases on the same PostgreSQL server.
For more information, see the relevant:
- [Omnibus GitLab issue](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5919).
- [Gitaly issue](https://gitlab.com/gitlab-org/gitaly/-/issues/3398).
## Is PgBouncer required for the Praefect database?
No, because the number of connections Praefect makes is low. You can use the same PgBouncer instance
for both the GitLab application database and the Praefect database if you wish.
## Are there any special considerations for Gitaly Cluster when PostgreSQL is upgraded?
There are no special requirements. Gitaly Cluster requires PostgreSQL version 11 or later.
## Praefect database tables are empty?
These tables are created per the [specific configuration section](praefect.md#postgresql).
If you find you have an empty Praefect database table, see the
[relevant troubleshooting section](troubleshooting.md#relation-does-not-exist-errors).
This document was moved to [another location](index.md).

View File

@ -206,6 +206,29 @@ WARNING:
If complete cluster failure occurs, disaster recovery plans should be executed. These can affect the
RPO and RTO discussed above.
### Comparison to Geo
Gitaly Cluster and [Geo](../geo/index.md) both provide redundancy. However the redundancy of:
- Gitaly Cluster provides fault tolerance for data storage and is invisible to the user. Users are
not aware when Gitaly Cluster is used.
- Geo provides [replication](../geo/index.md) and [disaster recovery](../geo/disaster_recovery/index.md) for
an entire instance of GitLab. Users know when they are using Geo for
[replication](../geo/index.md). Geo [replicates multiple data types](../geo/replication/datatypes.md#limitations-on-replicationverification),
including Git data.
The following table outlines the major differences between Gitaly Cluster and Geo:
| Tool | Nodes | Locations | Latency tolerance | Failover | Consistency | Provides redundancy for |
|:---------------|:---------|:----------|:------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------|:--------------------------------------|:------------------------|
| Gitaly Cluster | Multiple | Single | [Less than 1 second, ideally single-digit milliseconds](praefect.md#network-latency-and-connectivity) | [Automatic](praefect.md#automatic-failover-and-primary-election-strategies) | [Strong](index.md#strong-consistency) | Data storage in Git |
| Geo | Multiple | Multiple | Up to one minute | [Manual](../geo/disaster_recovery/index.md) | Eventual | Entire GitLab instance |
For more information, see:
- Geo [use cases](../geo/index.md#use-cases).
- Geo [architecture](../geo/index.md#architecture).
### Virtual storage
Virtual storage makes it viable to have a single repository storage in GitLab to simplify repository
@ -501,7 +524,7 @@ See the [Before deploying Gitaly Cluster](#before-deploying-gitaly-cluster) sect
for migrating to Gitaly Cluster involves:
1. Create the required storage. Refer to
[repository storage recommendations](faq.md#what-are-some-repository-storage-recommendations).
[repository storage recommendations](praefect.md#repository-storage-recommendations).
1. Create and configure [Gitaly Cluster](praefect.md).
1. [Move the repositories](../operations/moving_repositories.md#move-repositories). To migrate to
Gitaly Cluster, existing repositories stored outside Gitaly Cluster must be moved. There is no

View File

@ -54,7 +54,7 @@ Achieving acceptable latency between Gitaly nodes:
are designed for this type of synchronization. Latency of less than 2ms should be sufficient for Gitaly Cluster.
If you can't provide low network latencies for replication (for example, between distant locations), consider Geo. For
more information, see [How does Gitaly Cluster compare to Geo](faq.md#how-does-gitaly-cluster-compare-to-geo).
more information, see [Comparison to Geo](index.md#comparison-to-geo).
Gitaly Cluster [components](index.md#components) communicate with each other over many routes. Your firewall rules must
allow the following for Gitaly Cluster to function properly:
@ -74,6 +74,16 @@ Gitaly does not directly connect to Praefect. However, requests from Gitaly to t
load balancer may still be blocked unless firewalls on the Praefect nodes allow traffic from
the Gitaly nodes.
### Praefect database storage
The requirements are relatively low because the database contains only metadata of:
- Where repositories are located.
- Some queued work.
It depends on the number of repositories, but a useful minimum is 5-10 GB, similar to the main
GitLab application database.
## Setup Instructions
If you [installed](https://about.gitlab.com/install/) GitLab using the Omnibus GitLab package
@ -168,6 +178,18 @@ The following options are available:
- Use a cloud-managed PostgreSQL service. AWS
[Relational Database Service](https://aws.amazon.com/rds/) is recommended.
Setting up PostgreSQL creates empty Praefect tables. For more information, see the
[relevant troubleshooting section](troubleshooting.md#relation-does-not-exist-errors).
#### Running GitLab and Praefect databases on the same server
The GitLab application database and the Praefect database can be run on the same server. However, Praefect should have
its own database server when using Omnibus GitLab PostgreSQL. If there is a failover, Praefect isn't aware and starts to
fail as the database it's trying to use would either:
- Be unavailable.
- In read-only mode.
#### Manual database setup
To complete this section you need:
@ -278,8 +300,12 @@ reads distribution caching is enabled by configuration
#### Use PgBouncer
To reduce PostgreSQL resource consumption, we recommend setting up and configuring
[PgBouncer](https://www.pgbouncer.org/) in front of the PostgreSQL instance. To do
this, you must point Praefect to PgBouncer by setting database parameters on Praefect configuration:
[PgBouncer](https://www.pgbouncer.org/) in front of the PostgreSQL instance. However, PgBouncer isn't required because
Praefect makes a low number of connections. If you choose to use PgBouncer, you can use the same PgBouncer instance for
both the GitLab application database and the Praefect database.
To configure PgBouncer in front of the PostgreSQL instance, you must point Praefect to PgBouncer by setting database
parameters on Praefect configuration:
```ruby
praefect['database_host'] = PGBOUNCER_HOST
@ -1221,6 +1247,18 @@ You can configure:
If `default_replication_factor` is unset, the repositories are always replicated on every node defined in `virtual_storages`. If a new
node is introduced to the virtual storage, both new and existing repositories are replicated to the node automatically.
### Repository storage recommendations
The size of the required storage can vary between instances and depends on the set
[replication factor](index.md#replication-factor). You might want to include implementing
repository storage redundancy.
For a replication factor:
- Of `1`: NFS, Gitaly, and Gitaly Cluster have roughly the same storage requirements.
- More than `1`: The amount of required storage is `used space * replication factor`. `used space`
should include any planned future growth.
## Repository verification
> [Introduced](https://gitlab.com/gitlab-org/gitaly/-/issues/4080) in GitLab 15.0.

View File

@ -8,9 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Refer to the information below when troubleshooting Gitaly and Gitaly Cluster.
Before troubleshooting, see the Gitaly and Gitaly Cluster
[frequently asked questions](faq.md).
## Troubleshoot Gitaly
The following sections provide possible solutions to Gitaly errors.

View File

@ -17868,6 +17868,7 @@ Represents vulnerability letter grades with associated projects.
| <a id="workitemtitle"></a>`title` | [`String!`](#string) | Title of the work item. |
| <a id="workitemtitlehtml"></a>`titleHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `title`. |
| <a id="workitemuserpermissions"></a>`userPermissions` | [`WorkItemPermissions!`](#workitempermissions) | Permissions for the current user on the resource. |
| <a id="workitemwidgets"></a>`widgets` | [`[WorkItemWidget!]`](#workitemwidget) | Collection of widgets that belong to the work item. |
| <a id="workitemworkitemtype"></a>`workItemType` | [`WorkItemType!`](#workitemtype) | Type assigned to the work item. |
### `WorkItemPermissions`
@ -17892,6 +17893,18 @@ Check permissions for the current user on a work item.
| <a id="workitemtypeid"></a>`id` | [`WorkItemsTypeID!`](#workitemstypeid) | Global ID of the work item type. |
| <a id="workitemtypename"></a>`name` | [`String!`](#string) | Name of the work item type. |
### `WorkItemWidgetDescription`
Represents a description widget.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemwidgetdescriptiondescription"></a>`description` | [`String`](#string) | Description of the work item. |
| <a id="workitemwidgetdescriptiondescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
| <a id="workitemwidgetdescriptiontype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
## Enumeration types
Also called _Enums_, enumeration types are a special kind of scalar that
@ -19636,6 +19649,14 @@ Values for work item state events.
| <a id="workitemstateeventclose"></a>`CLOSE` | Closes the work item. |
| <a id="workitemstateeventreopen"></a>`REOPEN` | Reopens the work item. |
### `WorkItemWidgetType`
Type of a work item widget.
| Value | Description |
| ----- | ----------- |
| <a id="workitemwidgettypedescription"></a>`DESCRIPTION` | Description widget. |
## Scalar types
Scalar values are atomic values, and do not have fields of their own.
@ -20831,6 +20852,18 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="usertodosstate"></a>`state` | [`[TodoStateEnum!]`](#todostateenum) | State of the todo. |
| <a id="usertodostype"></a>`type` | [`[TodoTargetEnum!]`](#todotargetenum) | Type of the todo. |
#### `WorkItemWidget`
Implementations:
- [`WorkItemWidgetDescription`](#workitemwidgetdescription)
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemwidgettype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
## Input types
Types that may be used as arguments (all scalar types may also

View File

@ -13,11 +13,6 @@ for [the decomposed GitLab application using multiple databases](https://gitlab.
Learn more about general multiple databases support in a [separate document](multiple_databases.md).
WARNING:
If you experience any issues using `Gitlab::Database::Migration[2.0]`,
you can temporarily revert back to the previous behavior by changing the version to `Gitlab::Database::Migration[1.0]`.
Please report any issues with `Gitlab::Database::Migration[2.0]` in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/358430).
The design for multiple databases (except for the Geo database) assumes
that all decomposed databases have **the same structure** (for example, schema), but **the data is different** in each database. This means that some tables do not contain data on each database.

View File

@ -42,6 +42,7 @@ in the search field in the upper right corner:
> - Filtering by iterations was moved from GitLab Ultimate to GitLab Premium in 13.9.
> - Filtering by type was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/322755) in GitLab 13.10 [with a flag](../../administration/feature_flags.md) named `vue_issues_list`. Disabled by default.
> - Filtering by type was [enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/322755) in GitLab 14.10.
> - Filtering by type is generally available in GitLab 15.1. [Feature flag `vue_issues_list`](https://gitlab.com/gitlab-org/gitlab/-/issues/359966) removed.
> - Filtering by attention request was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/343528) in GitLab 14.10 [with a flag](../../administration/feature_flags.md) named `mr_attention_requests`. Disabled by default.
Follow these steps to filter the **Issues** and **Merge requests** list pages in projects and

View File

@ -22,8 +22,10 @@ module Gitlab
field name, GraphQL::Types::String, **kwargs
define_method resolver_method do
markdown_object = block_given? ? yield(object) : object
# We need to `dup` the context so the MarkdownHelper doesn't modify it
::MarkupHelper.markdown_field(object, method_name.to_sym, context.to_h.dup)
::MarkupHelper.markdown_field(markdown_object, method_name.to_sym, context.to_h.dup)
end
end
end

View File

@ -79,12 +79,6 @@ module QA
def has_no_issue?(issue)
has_no_element? :issuable_container, issuable_title: issue.title
end
def wait_for_vue_issues_list_ff
Support::Retrier.retry_until(max_duration: 60, reload_page: page, retry_on_exception: true, sleep_interval: 5) do
find_element(:closed_issuables_tab)
end
end
end
end
end

View File

@ -4,7 +4,6 @@ module QA
RSpec.describe(
'Plan',
:smoke,
feature_flag: { name: 'vue_issues_list', scope: :group },
quarantine: { issue: 'https://gitlab.com/gitlab-com/gl-infra/production/-/issues/7099', type: :investigating, only: { subdomain: 'pre' } }
) do
describe 'Issue creation' do
@ -12,8 +11,6 @@ module QA
let(:closed_issue) { Resource::Issue.fabricate_via_api! { |issue| issue.project = project } }
before do
Runtime::Feature.enable(:vue_issues_list, group: project.group)
Flow::Login.sign_in
end
@ -26,9 +23,6 @@ module QA
Page::Project::Menu.perform(&:click_issues)
# TODO: Remove this method when the `Runtime::Feature.enable` method call is removed
Page::Project::Issue::Index.perform(&:wait_for_vue_issues_list_ff)
Page::Project::Issue::Index.perform do |index|
expect(index).to have_issue(issue)
end
@ -49,9 +43,6 @@ module QA
Page::Project::Menu.perform(&:click_issues)
# TODO: Remove this method when the `Runtime::Feature.enable` method call is removed
Page::Project::Issue::Index.perform(&:wait_for_vue_issues_list_ff)
Page::Project::Issue::Index.perform do |index|
expect(index).not_to have_issue(closed_issue)

View File

@ -210,63 +210,6 @@ RSpec.describe Admin::ClustersController do
end
end
describe 'POST authorize AWS role for EKS cluster' do
let!(:role) { create(:aws_role, user: admin) }
let(:role_arn) { 'arn:new-role' }
let(:params) do
{
cluster: {
role_arn: role_arn
}
}
end
def go
post :authorize_aws_role, params: params
end
include_examples ':certificate_based_clusters feature flag controller responses' do
let(:subject) { go }
end
before do
allow(Clusters::Aws::FetchCredentialsService).to receive(:new)
.and_return(double(execute: double))
end
it 'updates the associated role with the supplied ARN' do
go
expect(response).to have_gitlab_http_status(:ok)
expect(role.reload.role_arn).to eq(role_arn)
end
context 'supplied role is invalid' do
let(:role_arn) { 'invalid-role' }
it 'does not update the associated role' do
expect { go }.not_to change { role.role_arn }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
describe 'security' do
before do
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
response = double(status: :ok, body: double)
allow(service).to receive(:execute).and_return(response)
end
end
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
end
describe 'DELETE clear cluster cache' do
let(:cluster) { create(:cluster, :instance) }
let!(:kubernetes_namespace) do

View File

@ -262,142 +262,6 @@ RSpec.describe Groups::ClustersController do
end
end
describe 'POST #create_aws' do
let(:params) do
{
cluster: {
name: 'new-cluster',
provider_aws_attributes: {
key_name: 'key',
role_arn: 'arn:role',
region: 'region',
vpc_id: 'vpc',
instance_type: 'instance type',
num_nodes: 3,
security_group_id: 'security group',
subnet_ids: %w(subnet1 subnet2)
}
}
}
end
def post_create_aws
post :create_aws, params: params.merge(group_id: group)
end
include_examples ':certificate_based_clusters feature flag controller responses' do
let(:subject) { post_create_aws }
end
it 'creates a new cluster' do
expect(ClusterProvisionWorker).to receive(:perform_async)
expect { post_create_aws }.to change { Clusters::Cluster.count }
.and change { Clusters::Providers::Aws.count }
cluster = group.clusters.first
expect(response).to have_gitlab_http_status(:created)
expect(response.location).to eq(group_cluster_path(group, cluster))
expect(cluster).to be_aws
expect(cluster).to be_kubernetes
end
context 'params are invalid' do
let(:params) do
{
cluster: { name: '' }
}
end
it 'does not create a cluster' do
expect { post_create_aws }.not_to change { Clusters::Cluster.count }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(response.media_type).to eq('application/json')
expect(response.body).to include('is invalid')
end
end
describe 'security' do
before do
allow(WaitForClusterCreationWorker).to receive(:perform_in)
end
it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { expect { post_create_aws }.to be_allowed_for(:admin) }
it('is denied for admin when admin mode is disabled') { expect { post_create_aws }.to be_denied_for(:admin) }
it { expect { post_create_aws }.to be_allowed_for(:owner).of(group) }
it { expect { post_create_aws }.to be_allowed_for(:maintainer).of(group) }
it { expect { post_create_aws }.to be_denied_for(:developer).of(group) }
it { expect { post_create_aws }.to be_denied_for(:reporter).of(group) }
it { expect { post_create_aws }.to be_denied_for(:guest).of(group) }
it { expect { post_create_aws }.to be_denied_for(:user) }
it { expect { post_create_aws }.to be_denied_for(:external) }
end
end
describe 'POST authorize AWS role for EKS cluster' do
let!(:role) { create(:aws_role, user: user) }
let(:role_arn) { 'arn:new-role' }
let(:params) do
{
cluster: {
role_arn: role_arn
}
}
end
def go
post :authorize_aws_role, params: params.merge(group_id: group)
end
include_examples ':certificate_based_clusters feature flag controller responses' do
let(:subject) { go }
end
before do
allow(Clusters::Aws::FetchCredentialsService).to receive(:new)
.and_return(double(execute: double))
end
it 'updates the associated role with the supplied ARN' do
go
expect(response).to have_gitlab_http_status(:ok)
expect(role.reload.role_arn).to eq(role_arn)
end
context 'supplied role is invalid' do
let(:role_arn) { 'invalid-role' }
it 'does not update the associated role' do
expect { go }.not_to change { role.role_arn }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
describe 'security' do
before do
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
response = double(status: :ok, body: double)
allow(service).to receive(:execute).and_return(response)
end
end
it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { expect { go }.to be_allowed_for(:admin) }
it('is denied for admin when admin mode is disabled') { expect { go }.to be_denied_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(group) }
it { expect { go }.to be_allowed_for(:maintainer).of(group) }
it { expect { go }.to be_denied_for(:developer).of(group) }
it { expect { go }.to be_denied_for(:reporter).of(group) }
it { expect { go }.to be_denied_for(:guest).of(group) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
end
describe 'DELETE clear cluster cache' do
let(:cluster) { create(:cluster, :group, groups: [group]) }
let!(:kubernetes_namespace) do

View File

@ -272,150 +272,6 @@ RSpec.describe Projects::ClustersController do
end
end
describe 'POST #create_aws' do
let(:params) do
{
cluster: {
name: 'new-cluster',
provider_aws_attributes: {
key_name: 'key',
role_arn: 'arn:role',
region: 'region',
vpc_id: 'vpc',
instance_type: 'instance type',
num_nodes: 3,
security_group_id: 'security group',
subnet_ids: %w(subnet1 subnet2)
}
}
}
end
def post_create_aws
post :create_aws, params: params.merge(namespace_id: project.namespace, project_id: project)
end
include_examples ':certificate_based_clusters feature flag controller responses' do
let(:subject) { post_create_aws }
end
it 'creates a new cluster' do
expect(ClusterProvisionWorker).to receive(:perform_async)
expect { post_create_aws }.to change { Clusters::Cluster.count }
.and change { Clusters::Providers::Aws.count }
cluster = project.clusters.first
expect(response).to have_gitlab_http_status(:created)
expect(response.location).to eq(project_cluster_path(project, cluster))
expect(cluster).to be_aws
expect(cluster).to be_kubernetes
end
context 'params are invalid' do
let(:params) do
{
cluster: { name: '' }
}
end
it 'does not create a cluster' do
expect { post_create_aws }.not_to change { Clusters::Cluster.count }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(response.media_type).to eq('application/json')
expect(response.body).to include('is invalid')
end
end
describe 'security' do
before do
allow(WaitForClusterCreationWorker).to receive(:perform_in)
end
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { post_create_aws }.to be_allowed_for(:admin)
end
it 'is disabled for admin when admin mode disabled' do
expect { post_create_aws }.to be_denied_for(:admin)
end
it { expect { post_create_aws }.to be_allowed_for(:owner).of(project) }
it { expect { post_create_aws }.to be_allowed_for(:maintainer).of(project) }
it { expect { post_create_aws }.to be_denied_for(:developer).of(project) }
it { expect { post_create_aws }.to be_denied_for(:reporter).of(project) }
it { expect { post_create_aws }.to be_denied_for(:guest).of(project) }
it { expect { post_create_aws }.to be_denied_for(:user) }
it { expect { post_create_aws }.to be_denied_for(:external) }
end
end
describe 'POST authorize AWS role for EKS cluster' do
let!(:role) { create(:aws_role, user: user) }
let(:role_arn) { 'arn:new-role' }
let(:params) do
{
cluster: {
role_arn: role_arn
}
}
end
def go
post :authorize_aws_role, params: params.merge(namespace_id: project.namespace, project_id: project)
end
before do
allow(Clusters::Aws::FetchCredentialsService).to receive(:new)
.and_return(double(execute: double))
end
include_examples ':certificate_based_clusters feature flag controller responses' do
let(:subject) { go }
end
it 'updates the associated role with the supplied ARN' do
go
expect(response).to have_gitlab_http_status(:ok)
expect(role.reload.role_arn).to eq(role_arn)
end
context 'supplied role is invalid' do
let(:role_arn) { 'invalid-role' }
it 'does not update the associated role' do
expect { go }.not_to change { role.role_arn }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
describe 'security' do
before do
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
response = double(status: :ok, body: double)
allow(service).to receive(:execute).and_return(response)
end
end
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { go }.to be_allowed_for(:admin)
end
it 'is disabled for admin when admin mode disabled' do
expect { go }.to be_denied_for(:admin)
end
it { expect { go }.to be_allowed_for(:owner).of(project) }
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
it { expect { go }.to be_denied_for(:developer).of(project) }
it { expect { go }.to be_denied_for(:reporter).of(project) }
it { expect { go }.to be_denied_for(:guest).of(project) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
end
describe 'DELETE clear cluster cache' do
let(:cluster) { create(:cluster, :project, projects: [project]) }
let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }

View File

@ -166,6 +166,14 @@ RSpec.describe Projects::CommitsController do
end
end
end
context 'with markdown cache' do
it 'preloads markdown cache for commits' do
expect(Commit).to receive(:preload_markdown_cache!).and_call_original
get :show, params: { namespace_id: project.namespace, project_id: project, id: 'master/README.md' }
end
end
end
describe "GET /commits/:id/signatures" do

View File

@ -25,19 +25,6 @@ RSpec.describe Projects::Settings::CiCdController do
expect(response).to render_template(:show)
end
context 'when the FF ci_owned_runners_cross_joins_fix is disabled' do
before do
stub_feature_flags(ci_owned_runners_cross_joins_fix: false)
end
it 'renders show with 200 status code' do
get :show, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
end
end
context 'with CI/CD disabled' do
before do
project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)

View File

@ -9,7 +9,7 @@ RSpec.describe 'Migrations Validation' do
let(:all_migration_classes) do
{
2022_01_26_21_06_58.. => Gitlab::Database::Migration[2.0],
2021_09_01_15_33_24.. => Gitlab::Database::Migration[1.0],
2021_09_01_15_33_24..2022_04_25_12_06_03 => Gitlab::Database::Migration[1.0],
2021_05_31_05_39_16..2021_09_01_15_33_24 => ActiveRecord::Migration[6.1],
..2021_05_31_05_39_16 => ActiveRecord::Migration[6.0]
}

View File

@ -36,20 +36,6 @@ RSpec.describe 'Clusterable > Show page' do
expect(page).not_to have_selector('[data-testid="cluster-environments-tab"]')
end
context 'content-security policy' do
it 'has AWS domains in the CSP' do
visit cluster_path
expect(response_headers['Content-Security-Policy']).to include(::Clusters::ClustersController::AWS_CSP_DOMAINS.join(' '))
end
it 'keeps existing connect-src in the CSP' do
visit cluster_path
expect(response_headers['Content-Security-Policy']).to include("connect-src #{Gitlab::ContentSecurityPolicy::Directives.connect_src}")
end
end
end
shared_examples 'editing a GCP cluster' do

View File

@ -10,7 +10,18 @@ RSpec.describe GitlabSchema.types['WorkItem'] do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::WorkItem) }
it 'has specific fields' do
fields = %i[description description_html id iid lock_version state title title_html userPermissions work_item_type]
fields = %i[
description
description_html
id
iid
lock_version
state title
title_html
userPermissions
widgets
work_item_type
]
fields.each do |field_name|
expect(described_class).to have_graphql_fields(*fields)

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::WorkItems::WidgetInterface do
include GraphqlHelpers
it 'exposes the expected fields' do
expected_fields = %i[type]
expect(described_class).to have_graphql_fields(*expected_fields)
end
describe ".resolve_type" do
it 'knows the correct type for objects' do
expect(
described_class.resolve_type(WorkItems::Widgets::Description.new(build(:work_item)), {})
).to eq(Types::WorkItems::Widgets::DescriptionType)
end
it 'raises an error for an unknown type' do
project = build(:project)
expect_graphql_error_to_be_created("Unknown GraphQL type for widget #{project}") do
described_class.resolve_type(project, {})
end
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['WorkItemWidgetType'] do
specify { expect(described_class.graphql_name).to eq('WorkItemWidgetType') }
it 'exposes all the existing widget type values' do
expect(described_class.values.transform_values { |v| v.value }).to include(
'DESCRIPTION' => :description
)
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::WorkItems::Widgets::DescriptionType do
it 'exposes the expected fields' do
expected_fields = %i[description description_html type]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end

View File

@ -55,6 +55,20 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
end
end
context 'when a block is passed for the resolved object' do
let(:type_class) do
class_with_markdown_field(:note_html, null: false) do |resolved_object|
resolved_object.object
end
end
let(:type_instance) { type_class.authorized_new(class_wrapped_object(note), context) }
it 'renders markdown from the same property as the field name without the `_html` suffix' do
expect(field.resolve(type_instance, {}, context)).to eq(expected_markdown)
end
end
describe 'basic verification that references work' do
let_it_be(:project) { create(:project, :public) }
@ -83,12 +97,22 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
end
end
def class_with_markdown_field(name, **args)
def class_with_markdown_field(name, **args, &blk)
Class.new(Types::BaseObject) do
prepend Gitlab::Graphql::MarkdownField
graphql_name 'MarkdownFieldTest'
markdown_field name, **args
markdown_field name, **args, &blk
end
end
def class_wrapped_object(object)
Class.new do
def initialize(object)
@object = object
end
attr_accessor :object
end.new(object)
end
end

View File

@ -4363,16 +4363,6 @@ RSpec.describe User do
it_behaves_like '#ci_owned_runners'
end
context 'when FF ci_owned_runners_cross_joins_fix is disabled' do
before do
skip_if_multiple_databases_are_setup
stub_feature_flags(ci_owned_runners_cross_joins_fix: false)
end
it_behaves_like '#ci_owned_runners'
end
context 'when FF ci_owned_runners_unnest_index is disabled uses GIN index' do
before do
stub_feature_flags(ci_owned_runners_unnest_index: false)

View File

@ -33,6 +33,12 @@ RSpec.describe WorkItem do
end
end
describe '#widgets' do
subject { build(:work_item).widgets }
it { is_expected.to contain_exactly(instance_of(WorkItems::Widgets::Description)) }
end
describe 'callbacks' do
describe 'record_create_action' do
it 'records the creation action after saving' do

View File

@ -60,7 +60,13 @@ RSpec.describe WorkItems::Type do
it { is_expected.not_to allow_value('s' * 256).for(:icon_name) }
end
describe 'default?' do
describe '.available_widgets' do
subject { described_class.available_widgets }
it { is_expected.to contain_exactly(::WorkItems::Widgets::Description) }
end
describe '#default?' do
subject { build(:work_item_type, namespace: namespace).default? }
context 'when namespace is nil' do

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WorkItems::Widgets::Base do
let_it_be(:work_item) { create(:work_item, description: '# Title') }
describe '.type' do
subject { described_class.type }
it { is_expected.to eq(:base) }
end
describe '#type' do
subject { described_class.new(work_item).type }
it { is_expected.to eq(:base) }
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WorkItems::Widgets::Description do
let_it_be(:work_item) { create(:work_item, description: '# Title') }
describe '.type' do
subject { described_class.type }
it { is_expected.to eq(:description) }
end
describe '#type' do
subject { described_class.new(work_item).type }
it { is_expected.to eq(:description) }
end
describe '#description' do
subject { described_class.new(work_item).description }
it { is_expected.to eq(work_item.description) }
end
end

View File

@ -7,7 +7,7 @@ RSpec.describe 'Query.work_item(id)' do
let_it_be(:developer) { create(:user) }
let_it_be(:project) { create(:project, :private).tap { |project| project.add_developer(developer) } }
let_it_be(:work_item) { create(:work_item, project: project) }
let_it_be(:work_item) { create(:work_item, project: project, description: '- List item') }
let(:current_user) { developer }
let(:work_item_data) { graphql_data['workItem'] }
@ -38,6 +38,34 @@ RSpec.describe 'Query.work_item(id)' do
)
end
context 'when querying widgets' do
let(:work_item_fields) do
<<~GRAPHQL
id
widgets {
type
... on WorkItemWidgetDescription {
description
descriptionHtml
}
}
GRAPHQL
end
it 'returns widget information' do
expect(work_item_data).to include(
'id' => work_item.to_gid.to_s,
'widgets' => contain_exactly(
hash_including(
'type' => 'DESCRIPTION',
'description' => work_item.description,
'descriptionHtml' => ::MarkupHelper.markdown_field(work_item, :description, {})
)
)
)
end
end
context 'when an Issue Global ID is provided' do
let(:global_id) { Issue.find(work_item.id).to_gid.to_s }