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
a18d587b
Commit
a18d587b
authored
Apr 02, 2021
by
Terri Chu
Committed by
Igor Drozdov
Apr 02, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Reduce queries on Controller Groups::LabelsController#index [RUN AS-IF-FOSS] [RUN ALL RSPEC]
parent
463d889e
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
112 additions
and
17 deletions
+112
-17
app/controllers/groups/labels_controller.rb
app/controllers/groups/labels_controller.rb
+2
-1
app/helpers/labels_helper.rb
app/helpers/labels_helper.rb
+3
-3
app/models/concerns/subscribable.rb
app/models/concerns/subscribable.rb
+35
-1
changelogs/unreleased/21034-controller-groups-labelscontroller-index-executes-more-than-100-sql.yml
...ups-labelscontroller-index-executes-more-than-100-sql.yml
+5
-0
spec/controllers/groups/labels_controller_spec.rb
spec/controllers/groups/labels_controller_spec.rb
+18
-0
spec/factories/subscriptions.rb
spec/factories/subscriptions.rb
+6
-0
spec/models/concerns/subscribable_spec.rb
spec/models/concerns/subscribable_spec.rb
+43
-12
No files found.
app/controllers/groups/labels_controller.rb
View file @
a18d587b
...
...
@@ -16,7 +16,8 @@ class Groups::LabelsController < Groups::ApplicationController
format
.
html
do
# at group level we do not want to list project labels,
# we only want `only_group_labels = false` when pulling labels for label filter dropdowns, fetched through json
@labels
=
available_labels
(
params
.
merge
(
only_group_labels:
true
)).
page
(
params
[
:page
])
@labels
=
available_labels
(
params
.
merge
(
only_group_labels:
true
)).
includes
(
:group
).
page
(
params
[
:page
])
# rubocop: disable CodeReuse/ActiveRecord
@labels
.
each
{
|
label
|
label
.
lazy_subscription
(
current_user
)
}
# preload subscriptions
end
format
.
json
do
render
json:
LabelSerializer
.
new
.
represent_appearance
(
available_labels
)
...
...
app/helpers/labels_helper.rb
View file @
a18d587b
...
...
@@ -164,8 +164,8 @@ module LabelsHelper
end
def
label_subscription_status
(
label
,
project
)
return
'group-level'
if
label
.
subscribed?
(
current_user
)
return
'project-level'
if
label
.
subscribed?
(
current_user
,
project
)
return
'group-level'
if
label
.
lazy_
subscribed?
(
current_user
)
return
'project-level'
if
label
.
lazy_
subscribed?
(
current_user
,
project
)
'unsubscribed'
end
...
...
@@ -181,7 +181,7 @@ module LabelsHelper
end
def
label_subscription_toggle_button_text
(
label
,
project
=
nil
)
label
.
subscribed?
(
current_user
,
project
)
?
'Unsubscribe'
:
'Subscribe'
label
.
lazy_
subscribed?
(
current_user
,
project
)
?
'Unsubscribe'
:
'Subscribe'
end
def
create_label_title
(
subject
)
...
...
app/models/concerns/subscribable.rb
View file @
a18d587b
...
...
@@ -17,13 +17,47 @@ module Subscribable
def
subscribed?
(
user
,
project
=
nil
)
return
false
unless
user
if
subscription
=
subscriptions
.
find_by
(
user:
user
,
project:
project
)
if
(
subscription
=
subscriptions
.
find_by
(
user:
user
,
project:
project
)
)
subscription
.
subscribed
else
subscribed_without_subscriptions?
(
user
,
project
)
end
end
def
lazy_subscribed?
(
user
,
project
=
nil
)
return
false
unless
user
if
(
subscription
=
lazy_subscription
(
user
,
project
)
&
.
itself
)
subscription
.
subscribed
else
subscribed_without_subscriptions?
(
user
,
project
)
end
end
def
lazy_subscription
(
user
,
project
=
nil
)
return
unless
user
# handle project and group labels as well as issuable subscriptions
subscribable_type
=
self
.
class
.
ancestors
.
include?
(
Label
)
?
'Label'
:
self
.
class
.
name
BatchLoader
.
for
(
id:
id
,
subscribable_type:
subscribable_type
,
project_id:
project
&
.
id
).
batch
do
|
items
,
loader
|
values
=
items
.
each_with_object
({
ids:
Set
.
new
,
subscribable_types:
Set
.
new
,
project_ids:
Set
.
new
})
do
|
item
,
result
|
result
[
:ids
]
<<
item
[
:id
]
result
[
:subscribable_types
]
<<
item
[
:subscribable_type
]
result
[
:project_ids
]
<<
item
[
:project_id
]
end
subscriptions
=
Subscription
.
where
(
subscribable_id:
values
[
:ids
],
subscribable_type:
values
[
:subscribable_types
],
project_id:
values
[
:project_ids
],
user:
user
)
subscriptions
.
each
do
|
subscription
|
loader
.
call
({
id:
subscription
.
subscribable_id
,
subscribable_type:
subscription
.
subscribable_type
,
project_id:
subscription
.
project_id
},
subscription
)
end
end
end
# Override this method to define custom logic to consider a subscribable as
# subscribed without an explicit subscription record.
def
subscribed_without_subscriptions?
(
user
,
project
)
...
...
changelogs/unreleased/21034-controller-groups-labelscontroller-index-executes-more-than-100-sql.yml
0 → 100644
View file @
a18d587b
---
title
:
Reduce queries on group labels controller
merge_request
:
57517
author
:
type
:
performance
spec/controllers/groups/labels_controller_spec.rb
View file @
a18d587b
...
...
@@ -46,6 +46,24 @@ RSpec.describe Groups::LabelsController do
it_behaves_like
'disabled when using an external authorization service'
end
context
'with views rendered'
do
render_views
before
do
get
:index
,
params:
{
group_id:
group
.
to_param
}
end
it
'avoids N+1 queries'
do
control
=
ActiveRecord
::
QueryRecorder
.
new
(
skip_cached:
false
)
{
get
:index
,
params:
{
group_id:
group
.
to_param
}
}
create_list
(
:group_label
,
3
,
group:
group
)
# some n+1 queries still exist
expect
{
get
:index
,
params:
{
group_id:
group
.
to_param
}
}.
not_to
exceed_all_query_limit
(
control
.
count
).
with_threshold
(
12
)
expect
(
assigns
(
:labels
).
count
).
to
eq
(
4
)
end
end
end
describe
'POST #toggle_subscription'
do
...
...
spec/factories/subscriptions.rb
View file @
a18d587b
...
...
@@ -5,5 +5,11 @@ FactoryBot.define do
project
user
{
project
.
creator
}
subscribable
factory: :issue
trait
:group_label
do
project
{
nil
}
user
{
association
(
:user
)
}
subscribable
factory: :group_label
end
end
end
spec/models/concerns/subscribable_spec.rb
View file @
a18d587b
...
...
@@ -7,50 +7,54 @@ RSpec.describe Subscribable, 'Subscribable' do
let
(
:resource
)
{
create
(
:issue
,
project:
project
)
}
let
(
:user_1
)
{
create
(
:user
)
}
describe
'#subscribed?'
do
shared_examples
'returns expected values'
do
|
method
|
context
'without user'
do
it
'returns false'
do
expect
(
resource
.
subscribed?
(
nil
,
project
)).
to
be_falsey
expect
(
resource
.
public_send
(
method
,
nil
,
project
)).
to
be_falsey
end
end
context
'without project'
do
it
'returns false when no subscription exists'
do
expect
(
resource
.
subscribed?
(
user_1
)).
to
be_falsey
expect
(
resource
.
public_send
(
method
,
user_1
)).
to
be_falsey
end
it
'returns true when a subcription exists and subscribed is true'
do
it
'returns true when a sub
s
cription exists and subscribed is true'
do
resource
.
subscriptions
.
create!
(
user:
user_1
,
subscribed:
true
)
expect
(
resource
.
subscribed?
(
user_1
)).
to
be_truthy
expect
(
resource
.
public_send
(
method
,
user_1
)).
to
be_truthy
end
it
'returns false when a subcription exists and subscribed is false'
do
it
'returns false when a sub
s
cription exists and subscribed is false'
do
resource
.
subscriptions
.
create!
(
user:
user_1
,
subscribed:
false
)
expect
(
resource
.
subscribed?
(
user_1
)).
to
be_falsey
expect
(
resource
.
public_send
(
method
,
user_1
)).
to
be_falsey
end
end
context
'with project'
do
it
'returns false when no subscription exists'
do
expect
(
resource
.
subscribed?
(
user_1
,
project
)).
to
be_falsey
expect
(
resource
.
public_send
(
method
,
user_1
,
project
)).
to
be_falsey
end
it
'returns true when a subcription exists and subscribed is true'
do
it
'returns true when a sub
s
cription exists and subscribed is true'
do
resource
.
subscriptions
.
create!
(
user:
user_1
,
project:
project
,
subscribed:
true
)
expect
(
resource
.
subscribed?
(
user_1
,
project
)).
to
be_truthy
expect
(
resource
.
public_send
(
method
,
user_1
,
project
)).
to
be_truthy
end
it
'returns false when a subcription exists and subscribed is false'
do
it
'returns false when a sub
s
cription exists and subscribed is false'
do
resource
.
subscriptions
.
create!
(
user:
user_1
,
project:
project
,
subscribed:
false
)
expect
(
resource
.
subscribed?
(
user_1
,
project
)).
to
be_falsey
expect
(
resource
.
public_send
(
method
,
user_1
,
project
)).
to
be_falsey
end
end
end
describe
'#subscribed?'
do
it_behaves_like
'returns expected values'
,
:subscribed?
end
describe
'#subscribers'
do
it
'returns [] when no subcribers exists'
do
expect
(
resource
.
subscribers
(
project
)).
to
be_empty
...
...
@@ -189,4 +193,31 @@ RSpec.describe Subscribable, 'Subscribable' do
it_behaves_like
'setting subscriptions'
end
end
describe
'#lazy_subscribed?'
do
it_behaves_like
'returns expected values'
,
:lazy_subscribed?
end
describe
'#lazy_subscription'
do
let
(
:labels
)
{
create_list
(
:group_label
,
5
)
}
before
do
labels
.
each
do
|
label
|
create
(
:subscription
,
:group_label
,
user:
user_1
,
subscribable:
label
)
end
end
it
'executes only one SQL query'
do
lazy_queries
=
ActiveRecord
::
QueryRecorder
.
new
do
labels
.
each
{
|
label
|
label
.
lazy_subscription
(
user_1
)
}
end
preloaded_queries
=
ActiveRecord
::
QueryRecorder
.
new
do
labels
.
each
{
|
label
|
label
.
lazy_subscription
(
user_1
)
&
.
subscribed?
}
end
expect
(
lazy_queries
.
count
).
to
eq
(
0
)
expect
(
preloaded_queries
.
count
).
to
eq
(
1
)
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