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
2ff257ec
Commit
2ff257ec
authored
Mar 31, 2021
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab master
parents
68815c34
8fe02ecd
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
68 additions
and
20 deletions
+68
-20
app/finders/pending_todos_finder.rb
app/finders/pending_todos_finder.rb
+6
-5
app/models/todo.rb
app/models/todo.rb
+4
-0
app/services/todo_service.rb
app/services/todo_service.rb
+10
-5
changelogs/unreleased/improve-todo-service-database-performance.yml
.../unreleased/improve-todo-service-database-performance.yml
+5
-0
spec/finders/pending_todos_finder_spec.rb
spec/finders/pending_todos_finder_spec.rb
+7
-5
spec/models/todo_spec.rb
spec/models/todo_spec.rb
+8
-0
spec/services/todo_service_spec.rb
spec/services/todo_service_spec.rb
+28
-5
No files found.
app/finders/pending_todos_finder.rb
View file @
2ff257ec
...
...
@@ -11,17 +11,18 @@
# change the various `by_*` methods in this finder, without having to touch
# everything that uses it.
class
PendingTodosFinder
attr_reader
:
current_user
,
:params
attr_reader
:
users
,
:params
#
current_user - The user
to retrieve the todos for.
#
users - The list of users
to retrieve the todos for.
# params - A Hash containing columns and values to use for filtering todos.
def
initialize
(
current_user
,
params
=
{})
@
current_user
=
current_user
def
initialize
(
users
,
params
=
{})
@
users
=
users
@params
=
params
end
def
execute
todos
=
current_user
.
todos
.
pending
todos
=
Todo
.
for_user
(
users
)
todos
=
todos
.
pending
todos
=
by_project
(
todos
)
todos
=
by_target_id
(
todos
)
todos
=
by_target_type
(
todos
)
...
...
app/models/todo.rb
View file @
2ff257ec
...
...
@@ -148,6 +148,10 @@ class Todo < ApplicationRecord
.
order
(
Gitlab
::
Database
.
nulls_last_order
(
'highest_priority'
,
'ASC'
))
.
order
(
'todos.created_at'
)
end
def
pluck_user_id
pluck
(
:user_id
)
end
end
def
resource_parent
...
...
app/services/todo_service.rb
View file @
2ff257ec
...
...
@@ -177,7 +177,7 @@ class TodoService
def
resolve_todos_for_target
(
target
,
current_user
)
attributes
=
attributes_for_target
(
target
)
resolve_todos
(
pending_todos
(
current_user
,
attributes
),
current_user
)
resolve_todos
(
pending_todos
(
[
current_user
]
,
attributes
),
current_user
)
end
def
resolve_todos
(
todos
,
current_user
,
resolution: :done
,
resolved_by_action: :system_done
)
...
...
@@ -220,9 +220,14 @@ class TodoService
private
def
create_todos
(
users
,
attributes
)
Array
(
users
).
map
do
|
user
|
next
if
pending_todos
(
user
,
attributes
).
exists?
&&
Feature
.
disabled?
(
:multiple_todos
,
user
)
users
=
Array
(
users
)
return
if
users
.
empty?
users_with_pending_todos
=
pending_todos
(
users
,
attributes
).
pluck_user_id
users
.
reject!
{
|
user
|
users_with_pending_todos
.
include?
(
user
.
id
)
&&
Feature
.
disabled?
(
:multiple_todos
,
user
)
}
users
.
map
do
|
user
|
issue_type
=
attributes
.
delete
(
:issue_type
)
track_todo_creation
(
user
,
issue_type
)
...
...
@@ -353,8 +358,8 @@ class TodoService
end
end
def
pending_todos
(
user
,
criteria
=
{})
PendingTodosFinder
.
new
(
user
,
criteria
).
execute
def
pending_todos
(
user
s
,
criteria
=
{})
PendingTodosFinder
.
new
(
user
s
,
criteria
).
execute
end
def
track_todo_creation
(
user
,
issue_type
)
...
...
changelogs/unreleased/improve-todo-service-database-performance.yml
0 → 100644
View file @
2ff257ec
---
title
:
Reduce N+1 queries in creating todos after user mentions in a note
merge_request
:
57525
author
:
type
:
performance
spec/finders/pending_todos_finder_spec.rb
View file @
2ff257ec
...
...
@@ -4,13 +4,15 @@ require 'spec_helper'
RSpec
.
describe
PendingTodosFinder
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:user2
)
{
create
(
:user
)
}
let
(
:users
)
{
[
user
,
user2
]
}
describe
'#execute'
do
it
'returns only pending todos'
do
create
(
:todo
,
:done
,
user:
user
)
todo
=
create
(
:todo
,
:pending
,
user:
user
)
todos
=
described_class
.
new
(
user
).
execute
todos
=
described_class
.
new
(
user
s
).
execute
expect
(
todos
).
to
eq
([
todo
])
end
...
...
@@ -22,7 +24,7 @@ RSpec.describe PendingTodosFinder do
create
(
:todo
,
:pending
,
user:
user
,
project:
project2
)
todo
=
create
(
:todo
,
:pending
,
user:
user
,
project:
project1
)
todos
=
described_class
.
new
(
user
,
project_id:
project1
.
id
).
execute
todos
=
described_class
.
new
(
user
s
,
project_id:
project1
.
id
).
execute
expect
(
todos
).
to
eq
([
todo
])
end
...
...
@@ -34,7 +36,7 @@ RSpec.describe PendingTodosFinder do
create
(
:todo
,
:pending
,
user:
user
,
target:
note
)
todos
=
described_class
.
new
(
user
,
target_id:
issue
.
id
).
execute
todos
=
described_class
.
new
(
user
s
,
target_id:
issue
.
id
).
execute
expect
(
todos
).
to
eq
([
todo
])
end
...
...
@@ -46,7 +48,7 @@ RSpec.describe PendingTodosFinder do
create
(
:todo
,
:pending
,
user:
user
,
target:
note
)
todos
=
described_class
.
new
(
user
,
target_type:
issue
.
class
.
name
).
execute
todos
=
described_class
.
new
(
user
s
,
target_type:
issue
.
class
.
name
).
execute
expect
(
todos
).
to
eq
([
todo
])
end
...
...
@@ -55,7 +57,7 @@ RSpec.describe PendingTodosFinder do
create
(
:todo
,
:pending
,
user:
user
,
commit_id:
'456'
)
todo
=
create
(
:todo
,
:pending
,
user:
user
,
commit_id:
'123'
)
todos
=
described_class
.
new
(
user
,
commit_id:
'123'
).
execute
todos
=
described_class
.
new
(
user
s
,
commit_id:
'123'
).
execute
expect
(
todos
).
to
eq
([
todo
])
end
...
...
spec/models/todo_spec.rb
View file @
2ff257ec
...
...
@@ -435,4 +435,12 @@ RSpec.describe Todo do
end
end
end
describe
'.pluck_user_id'
do
subject
{
described_class
.
pluck_user_id
}
let_it_be
(
:todo
)
{
create
(
:todo
)
}
it
{
is_expected
.
to
eq
([
todo
.
user_id
])
}
end
end
spec/services/todo_service_spec.rb
View file @
2ff257ec
...
...
@@ -1007,7 +1007,8 @@ RSpec.describe TodoService do
end
describe
'#update_note'
do
let
(
:noteable
)
{
create
(
:issue
,
project:
project
)
}
let_it_be
(
:noteable
)
{
create
(
:issue
,
project:
project
)
}
let
(
:note
)
{
create
(
:note
,
project:
project
,
note:
mentions
,
noteable:
noteable
)
}
let
(
:addressed_note
)
{
create
(
:note
,
project:
project
,
note:
"
#{
directly_addressed
}
"
,
noteable:
noteable
)
}
...
...
@@ -1044,12 +1045,34 @@ RSpec.describe TodoService do
should_not_create_todo
(
user:
skipped
,
target:
noteable
,
action:
Todo
::
DIRECTLY_ADDRESSED
)
end
it
'does not create a todo if user was already mentioned and todo is pending'
do
stub_feature_flags
(
multiple_todos:
false
)
context
'users already have pending todos and the multiple_todos feature is off'
do
before
do
stub_feature_flags
(
multiple_todos:
false
)
end
let_it_be
(
:pending_todo_for_member
)
{
create
(
:todo
,
:mentioned
,
user:
member
,
project:
project
,
target:
noteable
)
}
let_it_be
(
:pending_todo_for_guest
)
{
create
(
:todo
,
:mentioned
,
user:
guest
,
project:
project
,
target:
noteable
)
}
let_it_be
(
:pending_todo_for_admin
)
{
create
(
:todo
,
:mentioned
,
user:
admin
,
project:
project
,
target:
noteable
)
}
let_it_be
(
:note_mentioning_1_user
)
do
create
(
:note
,
project:
project
,
note:
"FYI
#{
member
.
to_reference
}
"
,
noteable:
noteable
)
end
create
(
:todo
,
:mentioned
,
user:
member
,
project:
project
,
target:
noteable
,
author:
author
)
let_it_be
(
:note_mentioning_3_users
)
do
create
(
:note
,
project:
project
,
note:
'FYI: '
+
[
member
,
guest
,
admin
].
map
(
&
:to_reference
).
join
(
' '
),
noteable:
noteable
)
end
it
'does not create a todo if user was already mentioned and todo is pending'
do
expect
{
service
.
update_note
(
note_mentioning_1_user
,
author
,
skip_users
)
}.
not_to
change
(
member
.
todos
,
:count
)
end
expect
{
service
.
update_note
(
note
,
author
,
skip_users
)
}.
not_to
change
(
member
.
todos
,
:count
)
it
'does not create N+1 queries for pending todos'
do
# Excluding queries for user permissions because those do execute N+1 queries
allow_any_instance_of
(
User
).
to
receive
(
:can?
).
and_return
(
true
)
control_count
=
ActiveRecord
::
QueryRecorder
.
new
{
service
.
update_note
(
note_mentioning_1_user
,
author
,
skip_users
)
}.
count
expect
{
service
.
update_note
(
note_mentioning_3_users
,
author
,
skip_users
)
}.
not_to
exceed_query_limit
(
control_count
)
end
end
it
'does not create a todo if user was already mentioned and todo is done'
do
...
...
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