Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
864dae0d98
commit
026a5e9101
|
@ -1 +1 @@
|
|||
ac989862106589558866e01fc5d77ad7326c99e4
|
||||
a6c5964bb455f77a0c79898f0b99c4c7df3aee3a
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -129,5 +129,8 @@
|
|||
"VulnerabilityLocationGeneric",
|
||||
"VulnerabilityLocationSast",
|
||||
"VulnerabilityLocationSecretDetection"
|
||||
],
|
||||
"WorkItemWidget": [
|
||||
"WorkItemWidgetDescription"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.'
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
module Widgets
|
||||
class Description < Base
|
||||
delegate :description, to: :work_item
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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 }
|
||||
|
||||
|
|
Loading…
Reference in New Issue