Commit 7c6bef47 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'kp-expose-discussion-notes-count-in-epics-graphql' into 'master'

Expose discussion and notes count in Epic query

See merge request gitlab-org/gitlab!47409
parents 3d1d57c2 8a2db291
...@@ -1802,6 +1802,16 @@ type BoardEpic implements CurrentUserTodos & Noteable { ...@@ -1802,6 +1802,16 @@ type BoardEpic implements CurrentUserTodos & Noteable {
""" """
upvotes: Int! upvotes: Int!
"""
Number of user discussions in the epic
"""
userDiscussionsCount: Int!
"""
Number of user notes of the epic
"""
userNotesCount: Int!
""" """
Permissions for the current user on the resource Permissions for the current user on the resource
""" """
...@@ -7250,6 +7260,16 @@ type Epic implements CurrentUserTodos & Noteable { ...@@ -7250,6 +7260,16 @@ type Epic implements CurrentUserTodos & Noteable {
""" """
upvotes: Int! upvotes: Int!
"""
Number of user discussions in the epic
"""
userDiscussionsCount: Int!
"""
Number of user notes of the epic
"""
userNotesCount: Int!
""" """
Permissions for the current user on the resource Permissions for the current user on the resource
""" """
......
...@@ -4753,6 +4753,42 @@ ...@@ -4753,6 +4753,42 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "userDiscussionsCount",
"description": "Number of user discussions in the epic",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "userNotesCount",
"description": "Number of user notes of the epic",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "userPermissions", "name": "userPermissions",
"description": "Permissions for the current user on the resource", "description": "Permissions for the current user on the resource",
...@@ -20055,6 +20091,42 @@ ...@@ -20055,6 +20091,42 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "userDiscussionsCount",
"description": "Number of user discussions in the epic",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "userNotesCount",
"description": "Number of user notes of the epic",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "userPermissions", "name": "userPermissions",
"description": "Permissions for the current user on the resource", "description": "Permissions for the current user on the resource",
...@@ -290,6 +290,8 @@ Represents an epic on an issue board. ...@@ -290,6 +290,8 @@ Represents an epic on an issue board.
| `title` | String | Title of the epic | | `title` | String | Title of the epic |
| `updatedAt` | Time | Timestamp of when the epic was updated | | `updatedAt` | Time | Timestamp of when the epic was updated |
| `upvotes` | Int! | Number of upvotes the epic has received | | `upvotes` | Int! | Number of upvotes the epic has received |
| `userDiscussionsCount` | Int! | Number of user discussions in the epic |
| `userNotesCount` | Int! | Number of user notes of the epic |
| `userPermissions` | EpicPermissions! | Permissions for the current user on the resource | | `userPermissions` | EpicPermissions! | Permissions for the current user on the resource |
| `userPreferences` | BoardEpicUserPreferences | User preferences for the epic on the issue board | | `userPreferences` | BoardEpicUserPreferences | User preferences for the epic on the issue board |
| `webPath` | String! | Web path of the epic | | `webPath` | String! | Web path of the epic |
...@@ -1189,6 +1191,8 @@ Represents an epic. ...@@ -1189,6 +1191,8 @@ Represents an epic.
| `title` | String | Title of the epic | | `title` | String | Title of the epic |
| `updatedAt` | Time | Timestamp of when the epic was updated | | `updatedAt` | Time | Timestamp of when the epic was updated |
| `upvotes` | Int! | Number of upvotes the epic has received | | `upvotes` | Int! | Number of upvotes the epic has received |
| `userDiscussionsCount` | Int! | Number of user discussions in the epic |
| `userNotesCount` | Int! | Number of user notes of the epic |
| `userPermissions` | EpicPermissions! | Permissions for the current user on the resource | | `userPermissions` | EpicPermissions! | Permissions for the current user on the resource |
| `webPath` | String! | Web path of the epic | | `webPath` | String! | Web path of the epic |
| `webUrl` | String! | Web URL of the epic | | `webUrl` | String! | Web URL of the epic |
......
...@@ -66,6 +66,11 @@ module Types ...@@ -66,6 +66,11 @@ module Types
field :downvotes, GraphQL::INT_TYPE, null: false, field :downvotes, GraphQL::INT_TYPE, null: false,
description: 'Number of downvotes the epic has received' description: 'Number of downvotes the epic has received'
field :user_notes_count, GraphQL::INT_TYPE, null: false,
description: 'Number of user notes of the epic'
field :user_discussions_count, GraphQL::INT_TYPE, null: false,
description: 'Number of user discussions in the epic'
field :closed_at, Types::TimeType, null: true, field :closed_at, Types::TimeType, null: true,
description: 'Timestamp of when the epic was closed' description: 'Timestamp of when the epic was closed'
field :created_at, Types::TimeType, null: true, field :created_at, Types::TimeType, null: true,
...@@ -144,6 +149,26 @@ module Types ...@@ -144,6 +149,26 @@ module Types
Epics::DescendantCountService.new(epic, ctx[:current_user]) Epics::DescendantCountService.new(epic, ctx[:current_user])
end end
def user_notes_count
BatchLoader::GraphQL.for(object.id).batch(key: :epic_user_notes_count) do |ids, loader, args|
counts = Note.count_for_collection(ids, 'Epic').index_by(&:noteable_id)
ids.each do |id|
loader.call(id, counts[id]&.count || 0)
end
end
end
def user_discussions_count
BatchLoader::GraphQL.for(object.id).batch(key: :epic_user_discussions_count) do |ids, loader, args|
counts = Note.count_for_collection(ids, 'Epic', 'COUNT(DISTINCT discussion_id) as count').index_by(&:noteable_id)
ids.each do |id|
loader.call(id, counts[id]&.count || 0)
end
end
end
def has_children? def has_children?
Gitlab::Graphql::Aggregations::Epics::LazyEpicAggregate.new(context, object.id, COUNT) do |node, _aggregate_object| Gitlab::Graphql::Aggregations::Epics::LazyEpicAggregate.new(context, object.id, COUNT) do |node, _aggregate_object|
node.children.any? node.children.any?
......
---
title: Expose discussion and notes count in Epic query
merge_request: 47409
author:
type: added
...@@ -11,8 +11,8 @@ RSpec.describe GitlabSchema.types['Epic'] do ...@@ -11,8 +11,8 @@ RSpec.describe GitlabSchema.types['Epic'] do
closed_at created_at updated_at children has_children has_issues has_parent closed_at created_at updated_at children has_children has_issues has_parent
web_path web_url relation_path reference issues user_permissions web_path web_url relation_path reference issues user_permissions
notes discussions relative_position subscribed participants notes discussions relative_position subscribed participants
descendant_counts descendant_weight_sum upvotes downvotes health_status descendant_counts descendant_weight_sum upvotes downvotes
current_user_todos user_notes_count user_discussions_count health_status current_user_todos
] ]
end end
......
...@@ -206,6 +206,47 @@ RSpec.describe 'Epics through GroupQuery' do ...@@ -206,6 +206,47 @@ RSpec.describe 'Epics through GroupQuery' do
end end
end end
describe 'N+1 query checks' do
let(:epic_a) { create(:epic, group: group) }
let(:epic_b) { create(:epic, group: group) }
let(:epics) { [epic_a, epic_b] }
let(:extra_iid_for_second_query) { epic_b.iid.to_s }
let(:search_params) { { iids: [epic_a.iid.to_s] } }
def execute_query
query = graphql_query_for(
:group,
{ full_path: group.full_path },
query_graphql_field(:epics, search_params, [
query_graphql_field(:nodes, nil, requested_fields)
])
)
post_graphql(query, current_user: user)
end
context 'when requesting `user_notes_count`' do
let(:requested_fields) { [:user_notes_count] }
before do
create_list(:note_on_epic, 2, noteable: epic_a)
create(:note_on_epic, noteable: epic_b)
end
include_examples 'N+1 query check'
end
context 'when requesting `user_discussions_count`' do
let(:requested_fields) { [:user_discussions_count] }
before do
create_list(:note_on_epic, 2, noteable: epic_a)
create(:note_on_epic, noteable: epic_b)
end
include_examples 'N+1 query check'
end
end
def expect_array_response(items) def expect_array_response(items)
expect(response).to have_gitlab_http_status(:success) expect(response).to have_gitlab_http_status(:success)
expect(epics_data).to be_an Array expect(epics_data).to be_an Array
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment