Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
26dfad7651
commit
bbc0882f57
2
Gemfile
2
Gemfile
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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"},
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.'
|
||||||
|
|
|
@ -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
|
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
527b18e3bd89316c33b099d4e3cd622617b6e8dbb482a0f0ce983386b0210f7e
|
|
@ -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);
|
||||||
|
|
|
@ -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.
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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' } }
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue