Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-03 06:07:46 +00:00
parent 26dfad7651
commit bbc0882f57
27 changed files with 345 additions and 71 deletions

View File

@ -332,7 +332,7 @@ gem 'sentry-sidekiq', '~> 5.1.1'
# PostgreSQL query parsing # PostgreSQL query parsing
# #
gem 'pg_query', '~> 2.1.4' gem 'pg_query', '~> 2.2'
gem 'premailer-rails', '~> 1.10.3' gem 'premailer-rails', '~> 1.10.3'

View File

@ -410,7 +410,7 @@
{"name":"pg","version":"1.4.3","platform":"x64-mingw-ucrt","checksum":"9f4d1d39af5ae5eea9f3c6b1e3092cbd5d26b716ff0e1283cf71c0690c69b36c"}, {"name":"pg","version":"1.4.3","platform":"x64-mingw-ucrt","checksum":"9f4d1d39af5ae5eea9f3c6b1e3092cbd5d26b716ff0e1283cf71c0690c69b36c"},
{"name":"pg","version":"1.4.3","platform":"x64-mingw32","checksum":"3265afd0e00331c7c70e50d4a13eea9083e5b683ebcd808bd671af70d92b189e"}, {"name":"pg","version":"1.4.3","platform":"x64-mingw32","checksum":"3265afd0e00331c7c70e50d4a13eea9083e5b683ebcd808bd671af70d92b189e"},
{"name":"pg","version":"1.4.3","platform":"x86-mingw32","checksum":"08a6ef4c702e313c1a04ad6b088b1843361ca8606843c7cd607e181e0d4e5508"}, {"name":"pg","version":"1.4.3","platform":"x86-mingw32","checksum":"08a6ef4c702e313c1a04ad6b088b1843361ca8606843c7cd607e181e0d4e5508"},
{"name":"pg_query","version":"2.1.4","platform":"ruby","checksum":"48f1363f88cf9d86fa11d76d1b0f839ca3723b8bd397b7cbc4b578e1ca82d0bb"}, {"name":"pg_query","version":"2.2.0","platform":"ruby","checksum":"84a37548412f540061bcc52ee2915352297832816bca60e4524c716e03f1e950"},
{"name":"plist","version":"3.6.0","platform":"ruby","checksum":"f468bcf6b72ec6d1585ed6744eb4817c1932a5bf91895ed056e69b7f12ca10f2"}, {"name":"plist","version":"3.6.0","platform":"ruby","checksum":"f468bcf6b72ec6d1585ed6744eb4817c1932a5bf91895ed056e69b7f12ca10f2"},
{"name":"png_quantizator","version":"0.2.1","platform":"ruby","checksum":"6023d4d064125c3a7e02929c95b7320ed6ac0d7341f9e8de0c9ea6576ef3106b"}, {"name":"png_quantizator","version":"0.2.1","platform":"ruby","checksum":"6023d4d064125c3a7e02929c95b7320ed6ac0d7341f9e8de0c9ea6576ef3106b"},
{"name":"po_to_json","version":"1.0.1","platform":"ruby","checksum":"6a7188aa6c42a22c9718f9b39062862ef7f3d8f6a7b4177cae058c3308b56af7"}, {"name":"po_to_json","version":"1.0.1","platform":"ruby","checksum":"6a7188aa6c42a22c9718f9b39062862ef7f3d8f6a7b4177cae058c3308b56af7"},

View File

@ -1034,7 +1034,7 @@ GEM
peek (1.1.0) peek (1.1.0)
railties (>= 4.0.0) railties (>= 4.0.0)
pg (1.4.3) pg (1.4.3)
pg_query (2.1.4) pg_query (2.2.0)
google-protobuf (>= 3.19.2) google-protobuf (>= 3.19.2)
plist (3.6.0) plist (3.6.0)
png_quantizator (0.2.1) png_quantizator (0.2.1)
@ -1730,7 +1730,7 @@ DEPENDENCIES
parslet (~> 1.8) parslet (~> 1.8)
peek (~> 1.1) peek (~> 1.1)
pg (~> 1.4.3) pg (~> 1.4.3)
pg_query (~> 2.1.4) pg_query (~> 2.2)
png_quantizator (~> 0.2.1) png_quantizator (~> 0.2.1)
premailer-rails (~> 1.10.3) premailer-rails (~> 1.10.3)
prometheus-client-mmap (~> 0.16) prometheus-client-mmap (~> 0.16)

View File

@ -9,7 +9,7 @@ module Resolvers
def resolve def resolve
authorize!(object) authorize!(object)
BatchLoader::GraphQL.for(object.id).batch(cache: false) do |ids, loader, args| BatchLoader::GraphQL.for(object.id).batch(key: object.class.name, cache: false) do |ids, loader, args|
labels = Label.for_targets(object.class.id_in(ids)).group_by(&:target_id) labels = Label.for_targets(object.class.id_in(ids)).group_by(&:target_id)
ids.each do |id| ids.each do |id|

View File

@ -14,6 +14,16 @@ module BoardItemFilterable
set_filter_values(filters[:not]) set_filter_values(filters[:not])
end end
if filters[:or]
if ::Feature.disabled?(:or_issuable_queries, resource_parent)
raise ::Gitlab::Graphql::Errors::ArgumentError,
"'or' arguments are only allowed when the `or_issuable_queries` feature flag is enabled."
end
rewrite_param_name(filters[:or], :author_usernames, :author_username)
rewrite_param_name(filters[:or], :assignee_usernames, :assignee_username)
end
filters filters
end end
@ -30,6 +40,14 @@ module BoardItemFilterable
filters[:assignee_id] = filters.delete(:assignee_wildcard_id) filters[:assignee_id] = filters.delete(:assignee_wildcard_id)
end end
end end
def rewrite_param_name(filters, old_name, new_name)
filters[new_name] = filters.delete(old_name) if filters[old_name].present?
end
def resource_parent
respond_to?(:board) ? board.resource_parent : list.board.resource_parent
end
end end
::BoardItemFilterable.prepend_mod_with('Resolvers::BoardItemFilterable') ::BoardItemFilterable.prepend_mod_with('Resolvers::BoardItemFilterable')

View File

@ -67,6 +67,9 @@ module IssueResolverArguments
argument :not, Types::Issues::NegatedIssueFilterInputType, argument :not, Types::Issues::NegatedIssueFilterInputType,
description: 'Negated arguments.', description: 'Negated arguments.',
required: false required: false
argument :or, Types::Issues::UnionedIssueFilterInputType,
description: 'List of arguments with inclusive OR.',
required: false
argument :crm_contact_id, GraphQL::Types::String, argument :crm_contact_id, GraphQL::Types::String,
required: false, required: false,
description: 'ID of a contact assigned to the issues.' description: 'ID of a contact assigned to the issues.'
@ -84,7 +87,12 @@ module IssueResolverArguments
end end
def ready?(**args) def ready?(**args)
args[:not] = args[:not].to_h if args[:not].present? if args[:or].present? && ::Feature.disabled?(:or_issuable_queries, resource_parent)
raise ::Gitlab::Graphql::Errors::ArgumentError, "'or' arguments are only allowed when the `or_issuable_queries` feature flag is enabled."
end
args[:not] = args[:not].to_h if args[:not]
args[:or] = args[:or].to_h if args[:or]
params_not_mutually_exclusive(args, mutually_exclusive_assignee_username_args) params_not_mutually_exclusive(args, mutually_exclusive_assignee_username_args)
params_not_mutually_exclusive(args, mutually_exclusive_milestone_args) params_not_mutually_exclusive(args, mutually_exclusive_milestone_args)
@ -116,9 +124,12 @@ module IssueResolverArguments
def prepare_finder_params(args) def prepare_finder_params(args)
params = super(args) params = super(args)
params[:not] = params[:not].to_h if params[:not]
params[:or] = params[:or].to_h if params[:or]
params[:iids] ||= [params.delete(:iid)].compact if params[:iid] params[:iids] ||= [params.delete(:iid)].compact if params[:iid]
params[:attempt_project_search_optimizations] = true if params[:search].present? params[:attempt_project_search_optimizations] = true if params[:search].present?
prepare_author_username_params(params)
prepare_assignee_username_params(params) prepare_assignee_username_params(params)
prepare_release_tag_params(params) prepare_release_tag_params(params)
@ -132,9 +143,14 @@ module IssueResolverArguments
args[:release_tag] ||= release_tag_wildcard args[:release_tag] ||= release_tag_wildcard
end end
def prepare_author_username_params(args)
args[:or][:author_username] = args[:or].delete(:author_usernames) if args.dig(:or, :author_usernames).present?
end
def prepare_assignee_username_params(args) def prepare_assignee_username_params(args)
args[:assignee_username] = args.delete(:assignee_usernames) if args[:assignee_usernames].present? args[:assignee_username] = args.delete(:assignee_usernames) if args[:assignee_usernames].present?
args[:not][:assignee_username] = args[:not].delete(:assignee_usernames) if args.dig(:not, :assignee_usernames).present? args[:not][:assignee_username] = args[:not].delete(:assignee_usernames) if args.dig(:not, :assignee_usernames).present?
args[:or][:assignee_username] = args[:or].delete(:assignee_usernames) if args.dig(:or, :assignee_usernames).present?
end end
def mutually_exclusive_release_tag_args def mutually_exclusive_release_tag_args

View File

@ -9,6 +9,10 @@ module Types
required: false, required: false,
description: 'List of negated arguments.' description: 'List of negated arguments.'
argument :or, Types::Issues::UnionedIssueFilterInputType,
required: false,
description: 'List of arguments with inclusive OR.'
argument :search, GraphQL::Types::String, argument :search, GraphQL::Types::String,
required: false, required: false,
description: 'Search query for issue title or description.' description: 'Search query for issue title or description.'

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
module Types
module Issues
class UnionedIssueFilterInputType < BaseInputObject
graphql_name 'UnionedIssueFilterInput'
argument :assignee_usernames, [GraphQL::Types::String],
required: false,
description: 'Filters issues that are assigned to at least one of the given users.'
argument :author_usernames, [GraphQL::Types::String],
required: false,
description: 'Filters issues that are authored by one of the given users.'
end
end
end

View File

@ -18,6 +18,8 @@ module IncidentManagement
validates :project, :incident, :occurred_at, presence: true validates :project, :incident, :occurred_at, presence: true
validates :action, presence: true, length: { maximum: 128 } validates :action, presence: true, length: { maximum: 128 }
# `user_input` is a note filled in by a user via API. Not auto generated by GitLab
validates :note, presence: true, length: { maximum: 280 }, on: :user_input
validates :note, presence: true, length: { maximum: 10_000 } validates :note, presence: true, length: { maximum: 10_000 }
validates :note_html, length: { maximum: 10_000 } validates :note_html, length: { maximum: 10_000 }

View File

@ -97,7 +97,7 @@ module IncidentManagement
timeline_event = IncidentManagement::TimelineEvent.new(timeline_event_params) timeline_event = IncidentManagement::TimelineEvent.new(timeline_event_params)
if timeline_event.save if timeline_event.save(context: validation_context)
add_system_note(timeline_event) add_system_note(timeline_event)
track_usage_event(:incident_management_timeline_event_created, user.id) track_usage_event(:incident_management_timeline_event_created, user.id)
@ -122,6 +122,10 @@ module IncidentManagement
SystemNoteService.add_timeline_event(timeline_event) SystemNoteService.add_timeline_event(timeline_event)
end end
def validation_context
:user_input if !auto_created && params[:promoted_from_note].blank?
end
end end
end end
end end

View File

@ -8,18 +8,23 @@ module IncidentManagement
# @option params [string] note # @option params [string] note
# @option params [datetime] occurred_at # @option params [datetime] occurred_at
class UpdateService < TimelineEvents::BaseService class UpdateService < TimelineEvents::BaseService
VALIDATION_CONTEXT = :user_input
def initialize(timeline_event, user, params) def initialize(timeline_event, user, params)
@timeline_event = timeline_event @timeline_event = timeline_event
@incident = timeline_event.incident @incident = timeline_event.incident
@user = user @user = user
@note = params[:note] @note = params[:note]
@occurred_at = params[:occurred_at] @occurred_at = params[:occurred_at]
@validation_context = VALIDATION_CONTEXT
end end
def execute def execute
return error_no_permissions unless allowed? return error_no_permissions unless allowed?
if timeline_event.update(update_params) timeline_event.assign_attributes(update_params)
if timeline_event.save(context: validation_context)
add_system_note(timeline_event) add_system_note(timeline_event)
track_usage_event(:incident_management_timeline_event_edited, user.id) track_usage_event(:incident_management_timeline_event_edited, user.id)
@ -31,7 +36,7 @@ module IncidentManagement
private private
attr_reader :timeline_event, :incident, :user, :note, :occurred_at attr_reader :timeline_event, :incident, :user, :note, :occurred_at, :validation_context
def update_params def update_params
{ updated_by_user: user, note: note, occurred_at: occurred_at }.compact { updated_by_user: user, note: note, occurred_at: occurred_at }.compact

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddIndexToTestReportsIssueIdCreatedAtAndId < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
TABLE_NAME = 'requirements_management_test_reports'
INDEX_NAME = 'idx_test_reports_on_issue_id_created_at_and_id'
def up
add_concurrent_index TABLE_NAME, [:issue_id, :created_at, :id], name: INDEX_NAME
end
def down
remove_concurrent_index_by_name TABLE_NAME, INDEX_NAME
end
end

View File

@ -0,0 +1 @@
527b18e3bd89316c33b099d4e3cd622617b6e8dbb482a0f0ce983386b0210f7e

View File

@ -27937,6 +27937,8 @@ CREATE UNIQUE INDEX idx_serverless_domain_cluster_on_clusters_applications_knati
CREATE INDEX idx_streaming_headers_on_external_audit_event_destination_id ON audit_events_streaming_headers USING btree (external_audit_event_destination_id); CREATE INDEX idx_streaming_headers_on_external_audit_event_destination_id ON audit_events_streaming_headers USING btree (external_audit_event_destination_id);
CREATE INDEX idx_test_reports_on_issue_id_created_at_and_id ON requirements_management_test_reports USING btree (issue_id, created_at, id);
CREATE INDEX idx_user_details_on_provisioned_by_group_id_user_id ON user_details USING btree (provisioned_by_group_id, user_id); CREATE INDEX idx_user_details_on_provisioned_by_group_id_user_id ON user_details USING btree (provisioned_by_group_id, user_id);
CREATE UNIQUE INDEX idx_vuln_signatures_on_occurrences_id_and_signature_sha ON vulnerability_finding_signatures USING btree (finding_id, signature_sha); CREATE UNIQUE INDEX idx_vuln_signatures_on_occurrences_id_and_signature_sha ON vulnerability_finding_signatures USING btree (finding_id, signature_sha);

View File

@ -376,14 +376,3 @@ sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\
/home/git/repositories \ /home/git/repositories \
/mnt/gitlab/repositories /mnt/gitlab/repositories
``` ```
## Troubleshooting
See the following for information on troubleshooting repository moves.
### Repository move fails for archived projects
Because of a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/363670),
[archived projects](../../user/project/settings/index.md#advanced-project-settings) fail to move even though the data is cloned
by Gitaly. Make sure archived projects are
[unarchived](../../user/project/settings/index.md#unarchive-a-project) before initiating a move.

View File

@ -13207,6 +13207,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupissuesmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. | | <a id="groupissuesmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. |
| <a id="groupissuesmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="groupissuesmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="groupissuesnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. | | <a id="groupissuesnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. |
| <a id="groupissuesor"></a>`or` | [`UnionedIssueFilterInput`](#unionedissuefilterinput) | List of arguments with inclusive OR. |
| <a id="groupissuessearch"></a>`search` | [`String`](#string) | Search query for title or description. | | <a id="groupissuessearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="groupissuessort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. | | <a id="groupissuessort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. |
| <a id="groupissuesstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. | | <a id="groupissuesstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. |
@ -16871,6 +16872,7 @@ Returns [`Issue`](#issue).
| <a id="projectissuemilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. | | <a id="projectissuemilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. |
| <a id="projectissuemyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="projectissuemyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="projectissuenot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. | | <a id="projectissuenot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. |
| <a id="projectissueor"></a>`or` | [`UnionedIssueFilterInput`](#unionedissuefilterinput) | List of arguments with inclusive OR. |
| <a id="projectissuereleasetag"></a>`releaseTag` | [`[String!]`](#string) | Release tag associated with the issue's milestone. | | <a id="projectissuereleasetag"></a>`releaseTag` | [`[String!]`](#string) | Release tag associated with the issue's milestone. |
| <a id="projectissuereleasetagwildcardid"></a>`releaseTagWildcardId` | [`ReleaseTagWildcardId`](#releasetagwildcardid) | Filter issues by release tag ID wildcard. | | <a id="projectissuereleasetagwildcardid"></a>`releaseTagWildcardId` | [`ReleaseTagWildcardId`](#releasetagwildcardid) | Filter issues by release tag ID wildcard. |
| <a id="projectissuesearch"></a>`search` | [`String`](#string) | Search query for title or description. | | <a id="projectissuesearch"></a>`search` | [`String`](#string) | Search query for title or description. |
@ -16910,6 +16912,7 @@ Returns [`IssueStatusCountsType`](#issuestatuscountstype).
| <a id="projectissuestatuscountsmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. | | <a id="projectissuestatuscountsmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. |
| <a id="projectissuestatuscountsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="projectissuestatuscountsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="projectissuestatuscountsnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. | | <a id="projectissuestatuscountsnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. |
| <a id="projectissuestatuscountsor"></a>`or` | [`UnionedIssueFilterInput`](#unionedissuefilterinput) | List of arguments with inclusive OR. |
| <a id="projectissuestatuscountsreleasetag"></a>`releaseTag` | [`[String!]`](#string) | Release tag associated with the issue's milestone. | | <a id="projectissuestatuscountsreleasetag"></a>`releaseTag` | [`[String!]`](#string) | Release tag associated with the issue's milestone. |
| <a id="projectissuestatuscountsreleasetagwildcardid"></a>`releaseTagWildcardId` | [`ReleaseTagWildcardId`](#releasetagwildcardid) | Filter issues by release tag ID wildcard. | | <a id="projectissuestatuscountsreleasetagwildcardid"></a>`releaseTagWildcardId` | [`ReleaseTagWildcardId`](#releasetagwildcardid) | Filter issues by release tag ID wildcard. |
| <a id="projectissuestatuscountssearch"></a>`search` | [`String`](#string) | Search query for title or description. | | <a id="projectissuestatuscountssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
@ -16956,6 +16959,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="projectissuesmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. | | <a id="projectissuesmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. |
| <a id="projectissuesmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="projectissuesmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="projectissuesnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. | | <a id="projectissuesnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. |
| <a id="projectissuesor"></a>`or` | [`UnionedIssueFilterInput`](#unionedissuefilterinput) | List of arguments with inclusive OR. |
| <a id="projectissuesreleasetag"></a>`releaseTag` | [`[String!]`](#string) | Release tag associated with the issue's milestone. | | <a id="projectissuesreleasetag"></a>`releaseTag` | [`[String!]`](#string) | Release tag associated with the issue's milestone. |
| <a id="projectissuesreleasetagwildcardid"></a>`releaseTagWildcardId` | [`ReleaseTagWildcardId`](#releasetagwildcardid) | Filter issues by release tag ID wildcard. | | <a id="projectissuesreleasetagwildcardid"></a>`releaseTagWildcardId` | [`ReleaseTagWildcardId`](#releasetagwildcardid) | Filter issues by release tag ID wildcard. |
| <a id="projectissuessearch"></a>`search` | [`String`](#string) | Search query for title or description. | | <a id="projectissuessearch"></a>`search` | [`String`](#string) | Search query for title or description. |
@ -23541,6 +23545,7 @@ Field that are available while modifying the custom mapping attributes for an HT
| <a id="boardissueinputmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter by milestone ID wildcard. | | <a id="boardissueinputmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter by milestone ID wildcard. |
| <a id="boardissueinputmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="boardissueinputmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="boardissueinputnot"></a>`not` | [`NegatedBoardIssueInput`](#negatedboardissueinput) | List of negated arguments. | | <a id="boardissueinputnot"></a>`not` | [`NegatedBoardIssueInput`](#negatedboardissueinput) | List of negated arguments. |
| <a id="boardissueinputor"></a>`or` | [`UnionedIssueFilterInput`](#unionedissuefilterinput) | List of arguments with inclusive OR. |
| <a id="boardissueinputreleasetag"></a>`releaseTag` | [`String`](#string) | Filter by release tag. | | <a id="boardissueinputreleasetag"></a>`releaseTag` | [`String`](#string) | Filter by release tag. |
| <a id="boardissueinputsearch"></a>`search` | [`String`](#string) | Search query for issue title or description. | | <a id="boardissueinputsearch"></a>`search` | [`String`](#string) | Search query for issue title or description. |
| <a id="boardissueinputtypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter by the given issue types. | | <a id="boardissueinputtypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter by the given issue types. |
@ -23945,6 +23950,15 @@ A time-frame defined as a closed inclusive range of two dates.
| <a id="timeframeend"></a>`end` | [`Date!`](#date) | End of the range. | | <a id="timeframeend"></a>`end` | [`Date!`](#date) | End of the range. |
| <a id="timeframestart"></a>`start` | [`Date!`](#date) | Start of the range. | | <a id="timeframestart"></a>`start` | [`Date!`](#date) | Start of the range. |
### `UnionedIssueFilterInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="unionedissuefilterinputassigneeusernames"></a>`assigneeUsernames` | [`[String!]`](#string) | Filters issues that are assigned to at least one of the given users. |
| <a id="unionedissuefilterinputauthorusernames"></a>`authorUsernames` | [`[String!]`](#string) | Filters issues that are authored by one of the given users. |
### `UpdateDiffImagePositionInput` ### `UpdateDiffImagePositionInput`
#### Arguments #### Arguments

View File

@ -27,6 +27,7 @@ RSpec.describe IncidentManagement::TimelineEvent do
it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:incident) } it { is_expected.to validate_presence_of(:incident) }
it { is_expected.to validate_presence_of(:note) } it { is_expected.to validate_presence_of(:note) }
it { is_expected.to validate_length_of(:note).is_at_most(280).on(:user_input) }
it { is_expected.to validate_length_of(:note).is_at_most(10_000) } it { is_expected.to validate_length_of(:note).is_at_most(10_000) }
it { is_expected.to validate_length_of(:note_html).is_at_most(10_000) } it { is_expected.to validate_length_of(:note_html).is_at_most(10_000) }
it { is_expected.to validate_presence_of(:occurred_at) } it { is_expected.to validate_presence_of(:occurred_at) }

View File

@ -21,6 +21,7 @@ RSpec.describe 'get board lists' do
let(:board_data) { graphql_data[board_parent_type]['boards']['nodes'][0] } let(:board_data) { graphql_data[board_parent_type]['boards']['nodes'][0] }
let(:lists_data) { board_data['lists']['nodes'][0] } let(:lists_data) { board_data['lists']['nodes'][0] }
let(:issues_data) { lists_data['issues']['nodes'] } let(:issues_data) { lists_data['issues']['nodes'] }
let(:issue_params) { { filters: { label_name: label2.title, confidential: confidential }, first: 3 } }
def query(list_params = params) def query(list_params = params)
graphql_query_for( graphql_query_for(
@ -31,7 +32,7 @@ RSpec.describe 'get board lists' do
nodes { nodes {
lists { lists {
nodes { nodes {
issues(filters: {labelName: "#{label2.title}", confidential: #{confidential}}, first: 3) { issues(#{attributes_to_graphql(issue_params)}) {
count count
nodes { nodes {
#{all_graphql_fields_for('issues'.classify)} #{all_graphql_fields_for('issues'.classify)}
@ -77,18 +78,23 @@ RSpec.describe 'get board lists' do
end end
context 'when user can read the board' do context 'when user can read the board' do
before do before_all do
board_parent.add_reporter(user) board_parent.add_reporter(user)
post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user)
end end
subject { post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user) }
it 'can access the issues', :aggregate_failures do it 'can access the issues', :aggregate_failures do
subject
# ties for relative positions are broken by id in ascending order by default # ties for relative positions are broken by id in ascending order by default
expect(issue_titles).to eq([issue2.title, issue1.title, issue3.title]) expect(issue_titles).to eq([issue2.title, issue1.title, issue3.title])
expect(issue_relative_positions).not_to include(nil) expect(issue_relative_positions).not_to include(nil)
end end
it 'does not set the relative positions of the issues not being returned', :aggregate_failures do it 'does not set the relative positions of the issues not being returned', :aggregate_failures do
subject
expect(issue_id).not_to include(issue6.id) expect(issue_id).not_to include(issue6.id)
expect(issue3.relative_position).to be_nil expect(issue3.relative_position).to be_nil
end end
@ -97,10 +103,36 @@ RSpec.describe 'get board lists' do
let(:confidential) { true } let(:confidential) { true }
it 'returns matching issue' do it 'returns matching issue' do
subject
expect(issue_titles).to match_array([issue7.title]) expect(issue_titles).to match_array([issue7.title])
expect(issue_relative_positions).not_to include(nil) expect(issue_relative_positions).not_to include(nil)
end end
end end
context 'when filtering by a unioned argument' do
let(:another_user) { create(:user) }
let(:issue_params) { { filters: { or: { assignee_usernames: [user.username, another_user.username] } } } }
it 'returns correctly filtered issues' do
issue1.assignee_ids = user.id
issue2.assignee_ids = another_user.id
subject
expect(issue_id).to contain_exactly(issue1.to_gid.to_s, issue2.to_gid.to_s)
end
context 'when feature flag is disabled' do
it 'returns an error' do
stub_feature_flags(or_issuable_queries: false)
subject
expect_graphql_errors_to_include("'or' arguments are only allowed when the `or_issuable_queries` feature flag is enabled.")
end
end
end
end end
end end

View File

@ -49,7 +49,7 @@ RSpec.describe 'get board lists' do
end end
shared_examples 'group and project board lists query' do shared_examples 'group and project board lists query' do
let!(:board) { create(:board, resource_parent: board_parent) } let_it_be(:board) { create(:board, resource_parent: board_parent) }
context 'when the user does not have access to the board' do context 'when the user does not have access to the board' do
it 'returns nil' do it 'returns nil' do
@ -107,16 +107,20 @@ RSpec.describe 'get board lists' do
end end
context 'when querying for a single list' do context 'when querying for a single list' do
let_it_be(:label_list) { create(:list, board: board, label: label, position: 10) }
let_it_be(:issues) do
[
create(:issue, project: project, labels: [label, label2]),
create(:issue, project: project, labels: [label, label2], confidential: true),
create(:issue, project: project, labels: [label])
]
end
before do before do
board_parent.add_reporter(user) board_parent.add_reporter(user)
end end
it 'returns the correct list with issue count for matching issue filters' do it 'returns the correct list with issue count for matching issue filters' do
label_list = create(:list, board: board, label: label, position: 10)
create(:issue, project: project, labels: [label, label2])
create(:issue, project: project, labels: [label, label2], confidential: true)
create(:issue, project: project, labels: [label])
post_graphql( post_graphql(
query( query(
id: global_id_of(label_list), id: global_id_of(label_list),
@ -131,21 +135,56 @@ RSpec.describe 'get board lists' do
expect(list_node['issuesCount']).to eq 1 expect(list_node['issuesCount']).to eq 1
end end
end end
context 'when filtering by a unioned argument' do
let_it_be(:another_user) { create(:user) }
it 'returns correctly filtered issues' do
issues[0].assignee_ids = user.id
issues[1].assignee_ids = another_user.id
post_graphql(
query(
id: global_id_of(label_list),
issueFilters: { or: { assignee_usernames: [user.username, another_user.username] } }
), current_user: user
)
expect(lists_data[0]['node']['issuesCount']).to eq 2
end
context 'when feature flag is disabled' do
it 'returns an error' do
stub_feature_flags(or_issuable_queries: false)
post_graphql(
query(
id: global_id_of(label_list),
issueFilters: { or: { assignee_usernames: [user.username, another_user.username] } }
), current_user: user
)
expect_graphql_errors_to_include(
"'or' arguments are only allowed when the `or_issuable_queries` feature flag is enabled."
)
end
end
end
end end
end end
describe 'for a project' do describe 'for a project' do
let(:board_parent) { project } let_it_be(:board_parent) { project }
let(:label) { project_label } let_it_be(:label) { project_label }
let(:label2) { project_label2 } let_it_be(:label2) { project_label2 }
it_behaves_like 'group and project board lists query' it_behaves_like 'group and project board lists query'
end end
describe 'for a group' do describe 'for a group' do
let(:board_parent) { group } let_it_be(:board_parent) { group }
let(:label) { group_label } let_it_be(:label) { group_label }
let(:label2) { group_label2 } let_it_be(:label2) { group_label2 }
before do before do
allow(board_parent).to receive(:multiple_issue_boards_available?).and_return(false) allow(board_parent).to receive(:multiple_issue_boards_available?).and_return(false)

View File

@ -57,4 +57,11 @@ RSpec.describe 'Creating an incident timeline event' do
'occurredAt' => event_occurred_at.iso8601 'occurredAt' => event_occurred_at.iso8601
) )
end end
context 'when note is more than 280 characters long' do
let_it_be(:note) { 'n' * 281 }
it_behaves_like 'timeline event mutation responds with validation error',
error_message: 'Timeline text is too long (maximum is 280 characters)'
end
end end

View File

@ -9,7 +9,7 @@ RSpec.describe 'Promote an incident timeline event from a comment' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:incident) { create(:incident, project: project) } let_it_be(:incident) { create(:incident, project: project) }
let_it_be(:comment) { create(:note, project: project, noteable: incident) } let_it_be(:comment) { create(:note, project: project, noteable: incident, note: 'a' * 281) }
let(:input) { { note_id: comment.to_global_id.to_s } } let(:input) { { note_id: comment.to_global_id.to_s } }
let(:mutation) do let(:mutation) do

View File

@ -13,11 +13,12 @@ RSpec.describe 'Updating an incident timeline event' do
end end
let(:occurred_at) { 1.minute.ago.iso8601 } let(:occurred_at) { 1.minute.ago.iso8601 }
let(:note) { 'Updated note' }
let(:variables) do let(:variables) do
{ {
id: timeline_event.to_global_id.to_s, id: timeline_event.to_global_id.to_s,
note: 'Updated note', note: note,
occurred_at: occurred_at occurred_at: occurred_at
} }
end end
@ -70,11 +71,18 @@ RSpec.describe 'Updating an incident timeline event' do
'id' => incident.to_global_id.to_s, 'id' => incident.to_global_id.to_s,
'title' => incident.title 'title' => incident.title
}, },
'note' => 'Updated note', 'note' => note,
'noteHtml' => timeline_event.note_html, 'noteHtml' => timeline_event.note_html,
'occurredAt' => occurred_at, 'occurredAt' => occurred_at,
'createdAt' => timeline_event.created_at.iso8601, 'createdAt' => timeline_event.created_at.iso8601,
'updatedAt' => timeline_event.updated_at.iso8601 'updatedAt' => timeline_event.updated_at.iso8601
) )
end end
context 'when note is more than 280 characters long' do
let(:note) { 'n' * 281 }
it_behaves_like 'timeline event mutation responds with validation error',
error_message: 'Timeline text is too long (maximum is 280 characters)'
end
end end

View File

@ -56,6 +56,62 @@ RSpec.describe 'getting an issue list for a project' do
end end
end end
context 'when filtering by a negated argument' do
let(:issue_filter_params) { { not: { assignee_usernames: current_user.username } } }
it 'returns correctly filtered issues' do
issue_a.assignee_ids = current_user.id
post_graphql(query, current_user: current_user)
expect(issues_ids).to contain_exactly(issue_b_gid)
end
context 'when argument is blank' do
let(:issue_filter_params) { { not: {} } }
it 'does not raise an error' do
post_graphql(query, current_user: current_user)
expect_graphql_errors_to_be_empty
end
end
end
context 'when filtering by a unioned argument' do
let(:another_user) { create(:user) }
let(:issue_filter_params) { { or: { assignee_usernames: [current_user.username, another_user.username] } } }
it 'returns correctly filtered issues' do
issue_a.assignee_ids = current_user.id
issue_b.assignee_ids = another_user.id
post_graphql(query, current_user: current_user)
expect(issues_ids).to contain_exactly(issue_a_gid, issue_b_gid)
end
context 'when argument is blank' do
let(:issue_filter_params) { { or: {} } }
it 'does not raise an error' do
post_graphql(query, current_user: current_user)
expect_graphql_errors_to_be_empty
end
end
context 'when feature flag is disabled' do
it 'returns an error' do
stub_feature_flags(or_issuable_queries: false)
post_graphql(query, current_user: current_user)
expect_graphql_errors_to_include("'or' arguments are only allowed when the `or_issuable_queries` feature flag is enabled.")
end
end
end
context 'filtering by my_reaction_emoji' do context 'filtering by my_reaction_emoji' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax

View File

@ -161,6 +161,38 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do
it 'successfully creates a database record', :aggregate_failures do it 'successfully creates a database record', :aggregate_failures do
expect { execute }.to change { ::IncidentManagement::TimelineEvent.count }.by(1) expect { execute }.to change { ::IncidentManagement::TimelineEvent.count }.by(1)
end end
context 'when note is more than 280 characters long' do
let(:args) do
{
note: 'a' * 281,
occurred_at: Time.current,
action: 'new comment',
promoted_from_note: comment,
auto_created: auto_created
}
end
let(:auto_created) { false }
context 'when was not promoted from note' do
let(:comment) { nil }
context 'when auto_created is true' do
let(:auto_created) { true }
it_behaves_like 'success response'
end
context 'when auto_created is false' do
it_behaves_like 'error response', 'Timeline text is too long (maximum is 280 characters)'
end
end
context 'when promoted from note' do
it_behaves_like 'success response'
end
end
end end
describe 'automatically created timeline events' do describe 'automatically created timeline events' do

View File

@ -87,6 +87,12 @@ RSpec.describe IncidentManagement::TimelineEvents::UpdateService do
it_behaves_like 'error response', "Timeline text can't be blank" it_behaves_like 'error response', "Timeline text can't be blank"
end end
context 'when note is more than 280 characters long' do
let(:params) { { note: 'n' * 281, occurred_at: occurred_at } }
it_behaves_like 'error response', 'Timeline text is too long (maximum is 280 characters)'
end
context 'when occurred_at is nil' do context 'when occurred_at is nil' do
let(:params) { { note: 'Updated note' } } let(:params) { { note: 'Updated note' } }

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
RSpec.shared_examples 'timeline event mutation responds with validation error' do |error_message:|
it 'responds with a validation error' do
post_graphql_mutation(mutation, current_user: user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['errors']).to match_array([error_message])
end
end

View File

@ -6,12 +6,18 @@ require 'fileutils'
RSpec.describe RepositoryCheck::SingleRepositoryWorker do RSpec.describe RepositoryCheck::SingleRepositoryWorker do
subject(:worker) { described_class.new } subject(:worker) { described_class.new }
before do
allow(::Gitlab::Git::Repository).to receive(:new).and_call_original
end
it 'skips when the project has no push events' do it 'skips when the project has no push events' do
project = create(:project, :repository, :wiki_disabled) project = create(:project, :repository, :wiki_disabled)
project.events.destroy_all # rubocop: disable Cop/DestroyAll project.events.destroy_all # rubocop: disable Cop/DestroyAll
break_project(project)
expect(worker).not_to receive(:git_fsck) repository = instance_double(::Gitlab::Git::Repository)
allow(::Gitlab::Git::Repository).to receive(:new)
.with(project.repository_storage, "#{project.disk_path}.git", anything, anything)
.and_return(repository)
worker.perform(project.id) worker.perform(project.id)
@ -21,7 +27,12 @@ RSpec.describe RepositoryCheck::SingleRepositoryWorker do
it 'fails when the project has push events and a broken repository' do it 'fails when the project has push events and a broken repository' do
project = create(:project, :repository) project = create(:project, :repository)
create_push_event(project) create_push_event(project)
break_project(project)
repository = project.repository.raw
expect(repository).to receive(:fsck).and_raise(::Gitlab::Git::Repository::GitError)
expect(::Gitlab::Git::Repository).to receive(:new)
.with(project.repository_storage, "#{project.disk_path}.git", anything, anything)
.and_return(repository)
worker.perform(project.id) worker.perform(project.id)
@ -32,7 +43,11 @@ RSpec.describe RepositoryCheck::SingleRepositoryWorker do
project = create(:project, :repository, :wiki_disabled) project = create(:project, :repository, :wiki_disabled)
create_push_event(project) create_push_event(project)
expect(worker).to receive(:git_fsck).and_call_original repository = project.repository.raw
expect(repository).to receive(:fsck).and_call_original
expect(::Gitlab::Git::Repository).to receive(:new)
.with(project.repository_storage, "#{project.disk_path}.git", anything, anything)
.and_return(repository)
expect do expect do
worker.perform(project.id) worker.perform(project.id)
@ -50,7 +65,12 @@ RSpec.describe RepositoryCheck::SingleRepositoryWorker do
worker.perform(project.id) worker.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(false) expect(project.reload.last_repository_check_failed).to eq(false)
break_wiki(project) repository = project.wiki.repository.raw
expect(repository).to receive(:fsck).and_raise(::Gitlab::Git::Repository::GitError)
expect(::Gitlab::Git::Repository).to receive(:new)
.with(project.repository_storage, "#{project.disk_path}.wiki.git", anything, anything)
.and_return(repository)
worker.perform(project.id) worker.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(true) expect(project.reload.last_repository_check_failed).to eq(true)
@ -59,7 +79,10 @@ RSpec.describe RepositoryCheck::SingleRepositoryWorker do
it 'skips wikis when disabled' do it 'skips wikis when disabled' do
project = create(:project, :wiki_disabled) project = create(:project, :wiki_disabled)
# Make sure the test would fail if the wiki repo was checked # Make sure the test would fail if the wiki repo was checked
break_wiki(project) repository = instance_double(::Gitlab::Git::Repository)
allow(::Gitlab::Git::Repository).to receive(:new)
.with(project.repository_storage, "#{project.disk_path}.wiki.git", anything, anything)
.and_return(repository)
subject.perform(project.id) subject.perform(project.id)
@ -88,31 +111,4 @@ RSpec.describe RepositoryCheck::SingleRepositoryWorker do
def create_push_event(project) def create_push_event(project)
project.events.create!(action: :pushed, author_id: create(:user).id) project.events.create!(action: :pushed, author_id: create(:user).id)
end end
def break_wiki(project)
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
break_repo(wiki_path(project))
end
end
def wiki_path(project)
project.wiki.repository.path_to_repo
end
def break_project(project)
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
break_repo(project.repository.path_to_repo)
end
end
def break_repo(repo)
# Create or replace blob ffffffffffffffffffffffffffffffffffffffff with an empty file
# This will make the repo invalid, _and_ 'git init' cannot fix it.
path = File.join(repo, 'objects', 'ff')
file = File.join(path, 'ffffffffffffffffffffffffffffffffffffff')
FileUtils.mkdir_p(path)
FileUtils.rm_f(file)
FileUtils.touch(file)
end
end end