Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
95dd6407
Commit
95dd6407
authored
Apr 06, 2021
by
Alex Kalderimis
Committed by
Igor Drozdov
Apr 06, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix excessive DB access in set_assignees mutation
parent
52c4b9a7
Changes
18
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
389 additions
and
69 deletions
+389
-69
app/graphql/mutations/concerns/mutations/assignable.rb
app/graphql/mutations/concerns/mutations/assignable.rb
+16
-13
app/graphql/mutations/merge_requests/set_assignees.rb
app/graphql/mutations/merge_requests/set_assignees.rb
+1
-1
app/graphql/types/mutation_operation_mode_enum.rb
app/graphql/types/mutation_operation_mode_enum.rb
+8
-0
app/services/merge_requests/update_assignees_service.rb
app/services/merge_requests/update_assignees_service.rb
+44
-0
app/workers/all_queues.yml
app/workers/all_queues.yml
+8
-0
app/workers/merge_requests/assignees_change_worker.rb
app/workers/merge_requests/assignees_change_worker.rb
+29
-0
changelogs/unreleased/ajk-36098-fix-excessive-db-activity-in-mr-assign-mutation.yml
...36098-fix-excessive-db-activity-in-mr-assign-mutation.yml
+5
-0
config/sidekiq_queues.yml
config/sidekiq_queues.yml
+2
-0
ee/app/services/ee/merge_requests/update_assignees_service.rb
...pp/services/ee/merge_requests/update_assignees_service.rb
+15
-0
ee/app/services/ee/merge_requests/update_service.rb
ee/app/services/ee/merge_requests/update_service.rb
+1
-3
ee/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
...pi/graphql/mutations/merge_requests/set_assignees_spec.rb
+53
-31
ee/spec/support/shared_examples/graphql/mutations/set_multiple_assignees_shared_examples.rb
...aphql/mutations/set_multiple_assignees_shared_examples.rb
+8
-1
spec/graphql/mutations/issues/set_assignees_spec.rb
spec/graphql/mutations/issues/set_assignees_spec.rb
+6
-1
spec/graphql/mutations/merge_requests/set_assignees_spec.rb
spec/graphql/mutations/merge_requests/set_assignees_spec.rb
+6
-1
spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
...pi/graphql/mutations/merge_requests/set_assignees_spec.rb
+38
-17
spec/services/merge_requests/update_assignees_service_spec.rb
.../services/merge_requests/update_assignees_service_spec.rb
+82
-0
spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb
...amples/graphql/mutations/set_assignees_shared_examples.rb
+8
-1
spec/workers/merge_requests/assignees_change_worker_spec.rb
spec/workers/merge_requests/assignees_change_worker_spec.rb
+59
-0
No files found.
app/graphql/mutations/concerns/mutations/assignable.rb
View file @
95dd6407
...
@@ -13,14 +13,13 @@ module Mutations
...
@@ -13,14 +13,13 @@ module Mutations
argument
:operation_mode
,
argument
:operation_mode
,
Types
::
MutationOperationModeEnum
,
Types
::
MutationOperationModeEnum
,
required:
false
,
required:
false
,
default_value:
Types
::
MutationOperationModeEnum
.
default_mode
,
description:
'The operation to perform. Defaults to REPLACE.'
description:
'The operation to perform. Defaults to REPLACE.'
end
end
def
resolve
(
project_path
:,
iid
:,
assignee_usernames
:,
operation_mode:
Types
::
MutationOperationModeEnum
.
enum
[
:replace
]
)
def
resolve
(
project_path
:,
iid
:,
assignee_usernames
:,
operation_mode
:)
resource
=
authorized_find!
(
project_path:
project_path
,
iid:
iid
)
resource
=
authorized_find!
(
project_path:
project_path
,
iid:
iid
)
Gitlab
::
QueryLimiting
.
disable!
(
'https://gitlab.com/gitlab-org/gitlab/issues/36098'
)
if
resource
.
is_a?
(
MergeRequest
)
update_service_class
.
new
(
update_service_class
.
new
(
resource
.
project
,
resource
.
project
,
current_user
,
current_user
,
...
@@ -35,18 +34,22 @@ module Mutations
...
@@ -35,18 +34,22 @@ module Mutations
private
private
def
assignee_ids
(
resource
,
usernames
,
operation_mode
)
def
assignee_ids
(
resource
,
usernames
,
mode
)
assignee_ids
=
[]
new
=
UsersFinder
.
new
(
current_user
,
username:
usernames
).
execute
.
map
(
&
:id
)
assignee_ids
+=
resource
.
assignees
.
map
(
&
:id
)
if
Types
::
MutationOperationModeEnum
.
enum
.
values_at
(
:remove
,
:append
).
include?
(
operation_mode
)
user_ids
=
UsersFinder
.
new
(
current_user
,
username:
usernames
).
execute
.
map
(
&
:id
)
transform_list
(
mode
,
resource
,
new
)
end
if
operation_mode
==
Types
::
MutationOperationModeEnum
.
enum
[
:remove
]
def
current_assignee_ids
(
resource
)
assignee_ids
-=
user_ids
resource
.
assignees
.
map
(
&
:id
)
else
assignee_ids
|=
user_ids
end
end
assignee_ids
def
transform_list
(
mode
,
resource
,
new_values
)
case
mode
when
'REPLACE'
then
new_values
when
'APPEND'
then
current_assignee_ids
(
resource
)
|
new_values
when
'REMOVE'
then
current_assignee_ids
(
resource
)
-
new_values
end
end
end
end
end
end
end
app/graphql/mutations/merge_requests/set_assignees.rb
View file @
95dd6407
...
@@ -8,7 +8,7 @@ module Mutations
...
@@ -8,7 +8,7 @@ module Mutations
include
Assignable
include
Assignable
def
update_service_class
def
update_service_class
::
MergeRequests
::
UpdateService
::
MergeRequests
::
Update
Assignees
Service
end
end
end
end
end
end
...
...
app/graphql/types/mutation_operation_mode_enum.rb
View file @
95dd6407
...
@@ -10,5 +10,13 @@ module Types
...
@@ -10,5 +10,13 @@ module Types
value
'REPLACE'
,
'Performs a replace operation.'
value
'REPLACE'
,
'Performs a replace operation.'
value
'APPEND'
,
'Performs an append operation.'
value
'APPEND'
,
'Performs an append operation.'
value
'REMOVE'
,
'Performs a removal operation.'
value
'REMOVE'
,
'Performs a removal operation.'
def
self
.
default_mode
enum
[
:replace
]
end
def
self
.
transform_modes
enum
.
values_at
(
:remove
,
:append
)
end
end
end
end
end
app/services/merge_requests/update_assignees_service.rb
0 → 100644
View file @
95dd6407
# frozen_string_literal: true
module
MergeRequests
class
UpdateAssigneesService
<
UpdateService
# a stripped down service that only does what it must to update the
# assignees, and knows that it does not have to check for other updates.
# This saves a lot of queries for irrelevant things that cannot possibly
# change in the execution of this service.
def
execute
(
merge_request
)
return
unless
current_user
&
.
can?
(
:update_merge_request
,
merge_request
)
old_ids
=
merge_request
.
assignees
.
map
(
&
:id
)
return
if
old_ids
.
to_set
==
update_attrs
[
:assignee_ids
].
to_set
# no-change
merge_request
.
update!
(
**
update_attrs
)
# Defer the more expensive operations (handle_assignee_changes) to the background
MergeRequests
::
AssigneesChangeWorker
.
perform_async
(
merge_request
.
id
,
current_user
.
id
,
old_ids
)
end
def
handle_assignee_changes
(
merge_request
,
old_assignees
)
# exposes private method from super-class
users
=
old_assignees
.
to_a
handle_assignees_change
(
merge_request
,
users
)
execute_hooks
(
merge_request
,
'update'
,
old_associations:
{
assignees:
users
}
)
end
private
def
assignee_ids
params
.
fetch
(
:assignee_ids
).
first
(
1
)
end
def
update_attrs
@attrs
||=
{
updated_at:
Time
.
current
,
updated_by:
current_user
,
assignee_ids:
assignee_ids
}
end
end
end
MergeRequests
::
UpdateAssigneesService
.
prepend_if_ee
(
'EE::MergeRequests::UpdateAssigneesService'
)
app/workers/all_queues.yml
View file @
95dd6407
...
@@ -1860,6 +1860,14 @@
...
@@ -1860,6 +1860,14 @@
:weight:
1
:weight:
1
:idempotent:
true
:idempotent:
true
:tags: []
:tags: []
-
:name: merge_requests_assignees_change
:feature_category: :source_code_management
:has_external_dependencies:
:urgency: :high
:resource_boundary: :unknown
:weight:
1
:idempotent:
true
:tags: []
-
:name: merge_requests_delete_source_branch
-
:name: merge_requests_delete_source_branch
:feature_category: :source_code_management
:feature_category: :source_code_management
:has_external_dependencies:
:has_external_dependencies:
...
...
app/workers/merge_requests/assignees_change_worker.rb
0 → 100644
View file @
95dd6407
# frozen_string_literal: true
class
MergeRequests::AssigneesChangeWorker
include
ApplicationWorker
feature_category
:source_code_management
urgency
:high
deduplicate
:until_executed
idempotent!
def
perform
(
merge_request_id
,
user_id
,
old_assignee_ids
)
merge_request
=
MergeRequest
.
find
(
merge_request_id
)
current_user
=
User
.
find
(
user_id
)
# if a user was added and then removed, or removed and then added
# while waiting for this job to run, assume that nothing happened.
users
=
User
.
id_in
(
old_assignee_ids
-
merge_request
.
assignee_ids
)
return
if
users
.
blank?
service
=
::
MergeRequests
::
UpdateAssigneesService
.
new
(
merge_request
.
target_project
,
current_user
)
service
.
handle_assignee_changes
(
merge_request
,
users
)
rescue
ActiveRecord
::
RecordNotFound
end
end
changelogs/unreleased/ajk-36098-fix-excessive-db-activity-in-mr-assign-mutation.yml
0 → 100644
View file @
95dd6407
---
title
:
Reduce number of queries in mergeRequestSetAssignees GraphQL mutation
merge_request
:
57523
author
:
type
:
performance
config/sidekiq_queues.yml
View file @
95dd6407
...
@@ -210,6 +210,8 @@
...
@@ -210,6 +210,8 @@
-
1
-
1
-
-
merge_request_reset_approvals
-
-
merge_request_reset_approvals
-
1
-
1
-
-
merge_requests_assignees_change
-
1
-
-
merge_requests_delete_source_branch
-
-
merge_requests_delete_source_branch
-
1
-
1
-
-
metrics_dashboard_prune_old_annotations
-
-
metrics_dashboard_prune_old_annotations
...
...
ee/app/services/ee/merge_requests/update_assignees_service.rb
0 → 100644
View file @
95dd6407
# frozen_string_literal: true
module
EE
module
MergeRequests
module
UpdateAssigneesService
def
assignee_ids
if
project
.
licensed_feature_available?
(
:multiple_merge_request_assignees
)
params
.
fetch
(
:assignee_ids
)
else
super
end
end
end
end
end
ee/app/services/ee/merge_requests/update_service.rb
View file @
95dd6407
...
@@ -18,9 +18,7 @@ module EE
...
@@ -18,9 +18,7 @@ module EE
merge_request
=
super
(
merge_request
)
merge_request
=
super
(
merge_request
)
if
should_remove_old_approvers
&&
merge_request
.
valid?
cleanup_approvers
(
merge_request
,
reload:
true
)
if
should_remove_old_approvers
&&
merge_request
.
valid?
cleanup_approvers
(
merge_request
,
reload:
true
)
end
merge_request
.
reset_approval_cache!
merge_request
.
reset_approval_cache!
...
...
ee/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
View file @
95dd6407
...
@@ -5,23 +5,21 @@ require 'spec_helper'
...
@@ -5,23 +5,21 @@ require 'spec_helper'
RSpec
.
describe
'Setting assignees of a merge request'
do
RSpec
.
describe
'Setting assignees of a merge request'
do
include
GraphqlHelpers
include
GraphqlHelpers
let
(
:current_user
)
{
create
(
:user
)
}
let_it_be
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:merge_request
)
{
create
(
:merge_request
)
}
let_it_be
(
:current_user
)
{
create
(
:user
,
developer_projects:
[
project
])
}
let
(
:project
)
{
merge_request
.
project
}
let_it_be
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
)
}
let
(
:assignees
)
{
create_list
(
:user
,
3
)
}
let_it_be
(
:assignees
)
{
create_list
(
:user
,
3
,
developer_projects:
[
project
])
}
let
(
:extra_assignees
)
{
create_list
(
:user
,
2
)
}
let_it_be
(
:extra_assignees
)
{
create_list
(
:user
,
2
,
developer_projects:
[
project
])
}
let
(
:input
)
{
{
assignee_usernames:
assignees
.
map
(
&
:username
)
}
}
let
(
:input
)
{
{
assignee_usernames:
assignees
.
map
(
&
:username
)
}
}
let
(
:expected_result
)
do
let
(
:expected_result
)
do
assignees
.
map
{
|
u
|
{
'username'
=>
u
.
username
}
}
assignees
.
map
{
|
u
|
{
'username'
=>
u
.
username
}
}
end
end
let
(
:mutation
)
do
def
mutation
(
vars
=
input
,
mr
=
merge_request
)
variables
=
{
variables
=
vars
.
merge
(
project_path:
mr
.
project
.
full_path
,
iid:
mr
.
iid
.
to_s
)
project_path:
project
.
full_path
,
iid:
merge_request
.
iid
.
to_s
graphql_mutation
(
:merge_request_set_assignees
,
variables
,
<<-
QL
.
strip_heredoc
)
}
graphql_mutation
(
:merge_request_set_assignees
,
variables
.
merge
(
input
),
<<-
QL
.
strip_heredoc
clientMutationId
clientMutationId
errors
errors
mergeRequest {
mergeRequest {
...
@@ -33,7 +31,6 @@ RSpec.describe 'Setting assignees of a merge request' do
...
@@ -33,7 +31,6 @@ RSpec.describe 'Setting assignees of a merge request' do
}
}
}
}
QL
QL
)
end
end
def
mutation_response
def
mutation_response
...
@@ -45,11 +42,7 @@ RSpec.describe 'Setting assignees of a merge request' do
...
@@ -45,11 +42,7 @@ RSpec.describe 'Setting assignees of a merge request' do
end
end
before
do
before
do
project
.
add_developer
(
current_user
)
[
current_user
,
*
assignees
,
*
extra_assignees
].
each
do
|
user
|
assignees
.
each
do
|
user
|
project
.
add_developer
(
user
)
end
extra_assignees
.
each
do
|
user
|
project
.
add_developer
(
user
)
project
.
add_developer
(
user
)
end
end
end
end
...
@@ -76,21 +69,50 @@ RSpec.describe 'Setting assignees of a merge request' do
...
@@ -76,21 +69,50 @@ RSpec.describe 'Setting assignees of a merge request' do
end
end
context
'when passing append as true'
do
context
'when passing append as true'
do
let
(
:input
)
{
{
assignee_usernames:
assignees
.
map
(
&
:username
),
operation_mode:
Types
::
MutationOperationModeEnum
.
enum
[
:append
]
}
}
let
(
:mode
)
{
Types
::
MutationOperationModeEnum
.
enum
[
:append
]
}
let
(
:usernames
)
{
assignees
.
map
(
&
:username
)
}
let
(
:input
)
{
{
operation_mode:
mode
}
}
let
(
:expected_result
)
do
let
(
:expected_result
)
do
assignees
.
map
{
|
u
|
{
'username'
=>
u
.
username
}
}
+
extra_assignees
.
map
{
|
u
|
{
'username'
=>
u
.
username
}
}
(
assignees
+
extra_assignees
)
.
map
{
|
u
|
{
'username'
=>
u
.
username
}
}
end
end
before
do
before
do
merge_request
.
reload
merge_request
.
assignees
=
extra_assignees
merge_request
.
assignees
=
extra_assignees
merge_request
.
save!
merge_request
.
save!
end
end
it
'does not remove users not in the list'
do
it
'does not remove users not in the list'
do
post_graphql_mutation
(
mutation
,
current_user:
current_user
)
vars
=
input
.
merge
(
assignee_usernames:
usernames
)
post_graphql_mutation
(
mutation
(
vars
),
current_user:
current_user
)
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
mutation_assignee_nodes
).
to
match_array
(
expected_result
)
expect
(
mutation_assignee_nodes
).
to
match_array
(
expected_result
)
end
end
describe
'performance'
do
it
'is scalable'
do
mr_a
=
create
(
:merge_request
,
:unique_branches
,
source_project:
project
)
mr_b
=
create
(
:merge_request
,
:unique_branches
,
source_project:
project
)
add_one_assignee
=
mutation
(
input
.
merge
(
assignee_usernames:
usernames
.
take
(
1
)),
mr_a
)
add_two_assignees
=
mutation
(
input
.
merge
(
assignee_usernames:
usernames
.
last
(
2
)),
mr_b
)
baseline
=
ActiveRecord
::
QueryRecorder
.
new
do
post_graphql_mutation
(
add_one_assignee
,
current_user:
current_user
)
end
# given the way ActiveRecord implements MergeRequest#assignee_ids=(ids),
# we to live with a slight inefficiency here:
# For each ID, AR issues:
# - SELECT 1 AS one FROM "merge_request_assignees"...
# Followed by:
# - INSERT INTO "merge_request_assignees" ("user_id", "merge_request_id", "created_at")...
expect
do
post_graphql_mutation
(
add_two_assignees
,
current_user:
current_user
)
end
.
not_to
exceed_query_limit
(
baseline
.
count
+
2
)
end
end
end
end
end
end
ee/spec/support/shared_examples/graphql/mutations/set_multiple_assignees_shared_examples.rb
View file @
95dd6407
...
@@ -9,10 +9,17 @@ RSpec.shared_examples 'a multi-assignable resource' do
...
@@ -9,10 +9,17 @@ RSpec.shared_examples 'a multi-assignable resource' do
describe
'#resolve'
do
describe
'#resolve'
do
let_it_be
(
:assignees
)
{
create_list
(
:user
,
3
)
}
let_it_be
(
:assignees
)
{
create_list
(
:user
,
3
)
}
let
(
:mode
)
{
Types
::
MutationOperationModeEnum
.
default_mode
}
let
(
:assignee_usernames
)
{
assignees
.
map
(
&
:username
)
}
let
(
:assignee_usernames
)
{
assignees
.
map
(
&
:username
)
}
let
(
:mutated_resource
)
{
subject
[
resource
.
class
.
name
.
underscore
.
to_sym
]
}
let
(
:mutated_resource
)
{
subject
[
resource
.
class
.
name
.
underscore
.
to_sym
]
}
subject
{
mutation
.
resolve
(
project_path:
resource
.
project
.
full_path
,
iid:
resource
.
iid
,
assignee_usernames:
assignee_usernames
)
}
subject
do
mutation
.
resolve
(
project_path:
resource
.
project
.
full_path
,
iid:
resource
.
iid
,
operation_mode:
mode
,
assignee_usernames:
assignee_usernames
)
end
before
do
before
do
assignees
.
each
do
|
user
|
assignees
.
each
do
|
user
|
...
...
spec/graphql/mutations/issues/set_assignees_spec.rb
View file @
95dd6407
...
@@ -11,7 +11,12 @@ RSpec.describe Mutations::Issues::SetAssignees do
...
@@ -11,7 +11,12 @@ RSpec.describe Mutations::Issues::SetAssignees do
subject
(
:mutation
)
{
described_class
.
new
(
object:
nil
,
context:
{
current_user:
user
},
field:
nil
)
}
subject
(
:mutation
)
{
described_class
.
new
(
object:
nil
,
context:
{
current_user:
user
},
field:
nil
)
}
describe
'#resolve'
do
describe
'#resolve'
do
subject
{
mutation
.
resolve
(
project_path:
issue
.
project
.
full_path
,
iid:
issue
.
iid
,
assignee_usernames:
[
assignee
.
username
])
}
subject
do
mutation
.
resolve
(
project_path:
issue
.
project
.
full_path
,
iid:
issue
.
iid
,
operation_mode:
Types
::
MutationOperationModeEnum
.
default_mode
,
assignee_usernames:
[
assignee
.
username
])
end
it_behaves_like
'permission level for issue mutation is correctly verified'
it_behaves_like
'permission level for issue mutation is correctly verified'
end
end
...
...
spec/graphql/mutations/merge_requests/set_assignees_spec.rb
View file @
95dd6407
...
@@ -11,7 +11,12 @@ RSpec.describe Mutations::MergeRequests::SetAssignees do
...
@@ -11,7 +11,12 @@ RSpec.describe Mutations::MergeRequests::SetAssignees do
subject
(
:mutation
)
{
described_class
.
new
(
object:
nil
,
context:
{
current_user:
user
},
field:
nil
)
}
subject
(
:mutation
)
{
described_class
.
new
(
object:
nil
,
context:
{
current_user:
user
},
field:
nil
)
}
describe
'#resolve'
do
describe
'#resolve'
do
subject
{
mutation
.
resolve
(
project_path:
merge_request
.
project
.
full_path
,
iid:
merge_request
.
iid
,
assignee_usernames:
[
assignee
.
username
])
}
subject
do
mutation
.
resolve
(
project_path:
merge_request
.
project
.
full_path
,
iid:
merge_request
.
iid
,
operation_mode:
described_class
.
arguments
[
'operationMode'
].
default_value
,
assignee_usernames:
[
assignee
.
username
])
end
it_behaves_like
'permission level for merge request mutation is correctly verified'
it_behaves_like
'permission level for merge request mutation is correctly verified'
end
end
...
...
spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
View file @
95dd6407
...
@@ -5,11 +5,12 @@ require 'spec_helper'
...
@@ -5,11 +5,12 @@ require 'spec_helper'
RSpec
.
describe
'Setting assignees of a merge request'
do
RSpec
.
describe
'Setting assignees of a merge request'
do
include
GraphqlHelpers
include
GraphqlHelpers
let
(
:current_user
)
{
create
(
:user
)
}
let_it_be
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:merge_request
)
{
create
(
:merge_request
)
}
let_it_be
(
:current_user
)
{
create
(
:user
,
developer_projects:
[
project
])
}
let
(
:project
)
{
merge_request
.
project
}
let_it_be
(
:assignee
)
{
create
(
:user
)
}
let
(
:assignee
)
{
create
(
:user
)
}
let_it_be
(
:assignee2
)
{
create
(
:user
)
}
let
(
:assignee2
)
{
create
(
:user
)
}
let_it_be_with_reload
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
)
}
let
(
:input
)
{
{
assignee_usernames:
[
assignee
.
username
]
}
}
let
(
:input
)
{
{
assignee_usernames:
[
assignee
.
username
]
}
}
let
(
:expected_result
)
do
let
(
:expected_result
)
do
[{
'username'
=>
assignee
.
username
}]
[{
'username'
=>
assignee
.
username
}]
...
@@ -44,10 +45,19 @@ RSpec.describe 'Setting assignees of a merge request' do
...
@@ -44,10 +45,19 @@ RSpec.describe 'Setting assignees of a merge request' do
mutation_response
[
'mergeRequest'
][
'assignees'
][
'nodes'
]
mutation_response
[
'mergeRequest'
][
'assignees'
][
'nodes'
]
end
end
def
run_mutation!
recorder
=
ActiveRecord
::
QueryRecorder
.
new
do
post_graphql_mutation
(
mutation
,
current_user:
current_user
)
end
expect
(
recorder
.
count
).
to
be
<=
db_query_limit
end
before
do
before
do
project
.
add_developer
(
current_user
)
project
.
add_developer
(
assignee
)
project
.
add_developer
(
assignee
)
project
.
add_developer
(
assignee2
)
project
.
add_developer
(
assignee2
)
merge_request
.
update!
(
assignees:
[])
end
end
it
'returns an error if the user is not allowed to update the merge request'
do
it
'returns an error if the user is not allowed to update the merge request'
do
...
@@ -56,23 +66,29 @@ RSpec.describe 'Setting assignees of a merge request' do
...
@@ -56,23 +66,29 @@ RSpec.describe 'Setting assignees of a merge request' do
expect
(
graphql_errors
).
not_to
be_empty
expect
(
graphql_errors
).
not_to
be_empty
end
end
it
'does not allow members without the right permission to add assignees'
do
context
'when the current user does not have permission to add assignees'
do
user
=
create
(
:user
)
let
(
:current_user
)
{
create
(
:user
)
}
project
.
add_guest
(
user
)
let
(
:db_query_limit
)
{
27
}
it
'does not change the assignees'
do
project
.
add_guest
(
current_user
)
post_graphql_mutation
(
mutation
,
current_user:
user
)
expect
{
run_mutation!
}.
not_to
change
{
merge_request
.
reset
.
assignees
.
pluck
(
:id
)
}
expect
(
graphql_errors
).
not_to
be_empty
expect
(
graphql_errors
).
not_to
be_empty
end
end
end
context
'with assignees already assigned'
do
context
'with assignees already assigned'
do
let
(
:db_query_limit
)
{
38
}
before
do
before
do
merge_request
.
assignees
=
[
assignee2
]
merge_request
.
assignees
=
[
assignee2
]
merge_request
.
save!
merge_request
.
save!
end
end
it
'replaces the assignee'
do
it
'replaces the assignee'
do
post_graphql_mutation
(
mutation
,
current_user:
current_user
)
run_mutation!
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
mutation_assignee_nodes
).
to
match_array
(
expected_result
)
expect
(
mutation_assignee_nodes
).
to
match_array
(
expected_result
)
...
@@ -80,6 +96,7 @@ RSpec.describe 'Setting assignees of a merge request' do
...
@@ -80,6 +96,7 @@ RSpec.describe 'Setting assignees of a merge request' do
end
end
context
'when passing an empty list of assignees'
do
context
'when passing an empty list of assignees'
do
let
(
:db_query_limit
)
{
31
}
let
(
:input
)
{
{
assignee_usernames:
[]
}
}
let
(
:input
)
{
{
assignee_usernames:
[]
}
}
before
do
before
do
...
@@ -88,7 +105,7 @@ RSpec.describe 'Setting assignees of a merge request' do
...
@@ -88,7 +105,7 @@ RSpec.describe 'Setting assignees of a merge request' do
end
end
it
'removes assignee'
do
it
'removes assignee'
do
post_graphql_mutation
(
mutation
,
current_user:
current_user
)
run_mutation!
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
mutation_assignee_nodes
).
to
eq
([])
expect
(
mutation_assignee_nodes
).
to
eq
([])
...
@@ -96,7 +113,9 @@ RSpec.describe 'Setting assignees of a merge request' do
...
@@ -96,7 +113,9 @@ RSpec.describe 'Setting assignees of a merge request' do
end
end
context
'when passing append as true'
do
context
'when passing append as true'
do
let
(
:input
)
{
{
assignee_usernames:
[
assignee2
.
username
],
operation_mode:
Types
::
MutationOperationModeEnum
.
enum
[
:append
]
}
}
let
(
:mode
)
{
Types
::
MutationOperationModeEnum
.
enum
[
:append
]
}
let
(
:input
)
{
{
assignee_usernames:
[
assignee2
.
username
],
operation_mode:
mode
}
}
let
(
:db_query_limit
)
{
20
}
before
do
before
do
# In CE, APPEND is a NOOP as you can't have multiple assignees
# In CE, APPEND is a NOOP as you can't have multiple assignees
...
@@ -108,7 +127,7 @@ RSpec.describe 'Setting assignees of a merge request' do
...
@@ -108,7 +127,7 @@ RSpec.describe 'Setting assignees of a merge request' do
end
end
it
'does not replace the assignee in CE'
do
it
'does not replace the assignee in CE'
do
post_graphql_mutation
(
mutation
,
current_user:
current_user
)
run_mutation!
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
mutation_assignee_nodes
).
to
match_array
(
expected_result
)
expect
(
mutation_assignee_nodes
).
to
match_array
(
expected_result
)
...
@@ -116,7 +135,9 @@ RSpec.describe 'Setting assignees of a merge request' do
...
@@ -116,7 +135,9 @@ RSpec.describe 'Setting assignees of a merge request' do
end
end
context
'when passing remove as true'
do
context
'when passing remove as true'
do
let
(
:input
)
{
{
assignee_usernames:
[
assignee
.
username
],
operation_mode:
Types
::
MutationOperationModeEnum
.
enum
[
:remove
]
}
}
let
(
:db_query_limit
)
{
31
}
let
(
:mode
)
{
Types
::
MutationOperationModeEnum
.
enum
[
:remove
]
}
let
(
:input
)
{
{
assignee_usernames:
[
assignee
.
username
],
operation_mode:
mode
}
}
let
(
:expected_result
)
{
[]
}
let
(
:expected_result
)
{
[]
}
before
do
before
do
...
@@ -125,7 +146,7 @@ RSpec.describe 'Setting assignees of a merge request' do
...
@@ -125,7 +146,7 @@ RSpec.describe 'Setting assignees of a merge request' do
end
end
it
'removes the users in the list, while adding none'
do
it
'removes the users in the list, while adding none'
do
post_graphql_mutation
(
mutation
,
current_user:
current_user
)
run_mutation!
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
mutation_assignee_nodes
).
to
match_array
(
expected_result
)
expect
(
mutation_assignee_nodes
).
to
match_array
(
expected_result
)
...
...
spec/services/merge_requests/update_assignees_service_spec.rb
0 → 100644
View file @
95dd6407
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
MergeRequests
::
UpdateAssigneesService
do
include
AfterNextHelpers
let_it_be
(
:group
)
{
create
(
:group
,
:public
)
}
let_it_be
(
:project
)
{
create
(
:project
,
:private
,
:repository
,
group:
group
)
}
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:user2
)
{
create
(
:user
)
}
let_it_be
(
:user3
)
{
create
(
:user
)
}
let_it_be_with_reload
(
:merge_request
)
do
create
(
:merge_request
,
:simple
,
:unique_branches
,
title:
'Old title'
,
description:
"FYI
#{
user2
.
to_reference
}
"
,
assignee_ids:
[
user3
.
id
],
source_project:
project
,
author:
create
(
:user
))
end
before
do
project
.
add_maintainer
(
user
)
project
.
add_developer
(
user2
)
project
.
add_developer
(
user3
)
end
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
opts
)
}
let
(
:opts
)
{
{
assignee_ids:
[
user2
.
id
]
}
}
describe
'execute'
do
def
update_merge_request
service
.
execute
(
merge_request
)
merge_request
.
reload
end
context
'when the parameters are valid'
do
it
'updates the MR, and queues the more expensive work for later'
do
expect
(
MergeRequests
::
AssigneesChangeWorker
)
.
to
receive
(
:perform_async
)
.
with
(
merge_request
.
id
,
user
.
id
,
[
user3
.
id
])
expect
{
update_merge_request
}
.
to
change
(
merge_request
,
:assignees
).
to
([
user2
])
.
and
change
(
merge_request
,
:updated_at
)
.
and
change
(
merge_request
,
:updated_by
).
to
(
user
)
end
it
'is more efficient than using the full update-service'
do
allow
(
MergeRequests
::
AssigneesChangeWorker
)
.
to
receive
(
:perform_async
)
.
with
(
merge_request
.
id
,
user
.
id
,
[
user3
.
id
])
other_mr
=
create
(
:merge_request
,
:simple
,
:unique_branches
,
title:
merge_request
.
title
,
description:
merge_request
.
description
,
assignee_ids:
merge_request
.
assignee_ids
,
source_project:
merge_request
.
project
,
author:
merge_request
.
author
)
update_service
=
::
MergeRequests
::
UpdateService
.
new
(
project
,
user
,
opts
)
expect
{
service
.
execute
(
merge_request
)
}
.
to
issue_fewer_queries_than
{
update_service
.
execute
(
other_mr
)
}
end
end
end
describe
'#handle_assignee_changes'
do
subject
{
service
.
handle_assignee_changes
(
merge_request
,
[
user2
])
}
it
'calls UpdateService#handle_assignee_changes and executes hooks'
do
expect
(
service
).
to
receive
(
:handle_assignees_change
).
with
(
merge_request
,
[
user2
])
expect
(
merge_request
.
project
).
to
receive
(
:execute_hooks
).
with
(
anything
,
:merge_request_hooks
)
expect
(
merge_request
.
project
).
to
receive
(
:execute_services
).
with
(
anything
,
:merge_request_hooks
)
expect
(
service
).
to
receive
(
:enqueue_jira_connect_messages_for
).
with
(
merge_request
)
subject
end
end
end
spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb
View file @
95dd6407
...
@@ -10,10 +10,17 @@ RSpec.shared_examples 'an assignable resource' do
...
@@ -10,10 +10,17 @@ RSpec.shared_examples 'an assignable resource' do
describe
'#resolve'
do
describe
'#resolve'
do
let_it_be
(
:assignee
)
{
create
(
:user
)
}
let_it_be
(
:assignee
)
{
create
(
:user
)
}
let_it_be
(
:assignee2
)
{
create
(
:user
)
}
let_it_be
(
:assignee2
)
{
create
(
:user
)
}
let
(
:assignee_usernames
)
{
[
assignee
.
username
]
}
let
(
:assignee_usernames
)
{
[
assignee
.
username
]
}
let
(
:mutated_resource
)
{
subject
[
resource
.
class
.
name
.
underscore
.
to_sym
]
}
let
(
:mutated_resource
)
{
subject
[
resource
.
class
.
name
.
underscore
.
to_sym
]
}
let
(
:mode
)
{
described_class
.
arguments
[
'operationMode'
].
default_value
}
subject
{
mutation
.
resolve
(
project_path:
resource
.
project
.
full_path
,
iid:
resource
.
iid
,
assignee_usernames:
assignee_usernames
)
}
subject
do
mutation
.
resolve
(
project_path:
resource
.
project
.
full_path
,
iid:
resource
.
iid
,
operation_mode:
mode
,
assignee_usernames:
assignee_usernames
)
end
before
do
before
do
resource
.
project
.
add_developer
(
assignee
)
resource
.
project
.
add_developer
(
assignee
)
...
...
spec/workers/merge_requests/assignees_change_worker_spec.rb
0 → 100644
View file @
95dd6407
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
MergeRequests
::
AssigneesChangeWorker
do
include
AfterNextHelpers
let_it_be
(
:merge_request
)
{
create
(
:merge_request
)
}
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:old_assignees
)
{
create_list
(
:user
,
3
)
}
let
(
:user_ids
)
{
old_assignees
.
map
(
&
:id
).
to_a
}
let
(
:worker
)
{
described_class
.
new
}
it_behaves_like
'an idempotent worker'
do
let
(
:job_args
)
{
[
merge_request
.
id
,
user
.
id
,
user_ids
]
}
end
describe
'#perform'
do
context
'with a non-existing merge request'
do
it
'does nothing'
do
expect
(
::
MergeRequests
::
UpdateAssigneesService
).
not_to
receive
(
:new
)
worker
.
perform
(
non_existing_record_id
,
user
.
id
,
user_ids
)
end
end
context
'with a non-existing user'
do
it
'does nothing'
do
expect
(
::
MergeRequests
::
UpdateAssigneesService
).
not_to
receive
(
:new
)
worker
.
perform
(
merge_request
.
id
,
non_existing_record_id
,
user_ids
)
end
end
context
'when there are no changes'
do
it
'does nothing'
do
expect
(
::
MergeRequests
::
UpdateAssigneesService
).
not_to
receive
(
:new
)
worker
.
perform
(
merge_request
.
id
,
user
.
id
,
merge_request
.
assignee_ids
)
end
end
context
'when the old users cannot be found'
do
it
'does nothing'
do
expect
(
::
MergeRequests
::
UpdateAssigneesService
).
not_to
receive
(
:new
)
worker
.
perform
(
merge_request
.
id
,
user
.
id
,
[
non_existing_record_id
])
end
end
it
'gets MergeRequests::UpdateAssigneesService to handle the changes'
do
expect_next
(
::
MergeRequests
::
UpdateAssigneesService
)
.
to
receive
(
:handle_assignee_changes
).
with
(
merge_request
,
old_assignees
)
worker
.
perform
(
merge_request
.
id
,
user
.
id
,
user_ids
)
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment