Skip to content
Snippets Groups Projects
Verified Commit b9dad9af authored by Heinrich Lee Yu's avatar Heinrich Lee Yu
Browse files

Add GraphQL field for sidebar counts

This allows us to render the sidebar counts asynchronously so we don't
block the rest of the page.
parent 06cf7af7
No related branches found
No related tags found
1 merge request!171238Add GraphQL field for sidebar counts
......@@ -107,6 +107,13 @@ class NamespaceType < BaseObject
extension(::Gitlab::Graphql::Limit::FieldCallCount, limit: 1)
end
field :sidebar,
Types::Namespaces::SidebarType,
null: true,
description: 'Data needed to render the sidebar for the namespace.',
method: :itself,
alpha: { milestone: '17.6' }
markdown_field :description_html, null: true
def achievements_path
......
# frozen_string_literal: true
module Types
module Namespaces
class SidebarType < BaseObject # rubocop:disable Graphql/AuthorizeTypes -- parent is already authorized
graphql_name 'NamespaceSidebar'
alias_method :namespace, :object
field :open_issues_count,
GraphQL::Types::Int,
null: true,
description: 'Number of open issues of the namespace.'
field :open_merge_requests_count, # rubocop:disable GraphQL/ExtractType -- no need to extract these into a field named "open"
GraphQL::Types::Int,
null: true,
description: 'Number of open merge requests of the namespace.'
def open_issues_count
case namespace
when ::Group
group_open_issues_count
when ::Namespaces::ProjectNamespace
namespace.project.open_issues_count(context[:current_user])
end
end
def open_merge_requests_count
case namespace
when Group
::Groups::MergeRequestsCountService.new(namespace, context[:current_user]).count
when ::Namespaces::ProjectNamespace
namespace.project.open_merge_requests_count
end
end
def group_open_issues_count
::Groups::OpenIssuesCountService.new(namespace, context[:current_user], fast_timeout: true).count
rescue ActiveRecord::QueryCanceled => e # rubocop:disable Database/RescueQueryCanceled -- used with fast_read_statement_timeout to prevent this count from slowing down the rest of the request
Gitlab::ErrorTracking.log_exception(e, group_id: namespace.id, query: 'group_sidebar_issues_count')
nil
end
end
end
end
Types::Namespaces::SidebarType.prepend_mod
......@@ -23943,6 +23943,7 @@ GPG signature for a signed commit.
| <a id="groupsecuritypolicyproject"></a>`securityPolicyProject` | [`Project`](#project) | Security policy project assigned to the namespace. |
| <a id="groupsharewithgrouplock"></a>`shareWithGroupLock` | [`Boolean`](#boolean) | Indicates if sharing a project with another group within this group is prevented. |
| <a id="groupsharedrunnerssetting"></a>`sharedRunnersSetting` | [`SharedRunnersSetting`](#sharedrunnerssetting) | Shared runners availability for the namespace and its descendants. |
| <a id="groupsidebar"></a>`sidebar` **{warning-solid}** | [`NamespaceSidebar`](#namespacesidebar) | **Introduced** in GitLab 17.6. **Status**: Experiment. Data needed to render the sidebar for the namespace. |
| <a id="groupstandardroles"></a>`standardRoles` **{warning-solid}** | [`StandardRoleConnection`](#standardroleconnection) | **Introduced** in GitLab 17.4. **Status**: Experiment. Standard roles available for the instance, available only for self-managed. |
| <a id="groupstats"></a>`stats` | [`GroupStats`](#groupstats) | Group statistics. |
| <a id="groupstoragesizelimit"></a>`storageSizeLimit` | [`Float`](#float) | The storage limit (in bytes) included with the root namespace plan. This limit only applies to namespaces under namespace limit enforcement. |
......@@ -28815,6 +28816,7 @@ Product analytics events for a specific month and year.
| <a id="namespacerootstoragestatistics"></a>`rootStorageStatistics` | [`RootStorageStatistics`](#rootstoragestatistics) | Aggregated storage statistics of the namespace. Only available for root namespaces. |
| <a id="namespacesecuritypolicyproject"></a>`securityPolicyProject` | [`Project`](#project) | Security policy project assigned to the namespace. |
| <a id="namespacesharedrunnerssetting"></a>`sharedRunnersSetting` | [`SharedRunnersSetting`](#sharedrunnerssetting) | Shared runners availability for the namespace and its descendants. |
| <a id="namespacesidebar"></a>`sidebar` **{warning-solid}** | [`NamespaceSidebar`](#namespacesidebar) | **Introduced** in GitLab 17.6. **Status**: Experiment. Data needed to render the sidebar for the namespace. |
| <a id="namespacestoragesizelimit"></a>`storageSizeLimit` | [`Float`](#float) | The storage limit (in bytes) included with the root namespace plan. This limit only applies to namespaces under namespace limit enforcement. |
| <a id="namespacesubscriptionhistory"></a>`subscriptionHistory` **{warning-solid}** | [`GitlabSubscriptionHistoryConnection`](#gitlabsubscriptionhistoryconnection) | **Introduced** in GitLab 17.3. **Status**: Experiment. Find subscription history records. |
| <a id="namespacetimelogcategories"></a>`timelogCategories` **{warning-solid}** | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | **Introduced** in GitLab 15.3. **Status**: Experiment. Timelog categories for the namespace. |
......@@ -29127,6 +29129,16 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="namespacecommitemailnamespace"></a>`namespace` | [`Namespace!`](#namespace) | Namespace. |
| <a id="namespacecommitemailupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp the namespace commit email was last updated. |
 
### `NamespaceSidebar`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="namespacesidebaropenepicscount"></a>`openEpicsCount` | [`Int`](#int) | Number of open epics of the namespace. |
| <a id="namespacesidebaropenissuescount"></a>`openIssuesCount` | [`Int`](#int) | Number of open issues of the namespace. |
| <a id="namespacesidebaropenmergerequestscount"></a>`openMergeRequestsCount` | [`Int`](#int) | Number of open merge requests of the namespace. |
### `NestedEnvironment`
 
Describes where code is deployed for a project organized by folder.
# frozen_string_literal: true
module EE
module Types
module Namespaces
module SidebarType
extend ActiveSupport::Concern
prepended do
field :open_epics_count,
GraphQL::Types::Int,
null: true,
description: 'Number of open epics of the namespace.'
end
def open_epics_count
return unless namespace.is_a?(Group)
::Groups::EpicsCountService.new(namespace, context[:current_user]).count
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['NamespaceSidebar'], feature_category: :navigation do
let(:fields) do
%i[open_issues_count open_merge_requests_count open_epics_count]
end
specify { expect(described_class).to have_graphql_fields(fields) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Namespace.sidebar', feature_category: :navigation do
include GraphqlHelpers
let_it_be(:group) { create(:group, :private) }
let_it_be(:project) { create(:project, :repository, namespace: group) }
let_it_be(:reporter) { create(:user, reporter_of: group) }
let(:query) do
<<~QUERY
query {
namespace(fullPath: "#{namespace.full_path}") {
sidebar {
openEpicsCount
}
}
}
QUERY
end
before_all do
create_list(:epic, 2, group: group)
end
before do
stub_licensed_features(epics: true)
end
context 'with a Group' do
let(:namespace) { group }
it 'returns the epic counts' do
post_graphql(query, current_user: reporter)
expect(response).to have_gitlab_http_status(:ok)
expect(graphql_data_at(:namespace, :sidebar)).to eq({
'openEpicsCount' => 2
})
end
end
context 'with a ProjectNamespace' do
let(:namespace) { project.project_namespace }
it 'returns nil epic count' do
post_graphql(query, current_user: reporter)
expect(response).to have_gitlab_http_status(:ok)
expect(graphql_data_at(:namespace, :sidebar)).to eq({
'openEpicsCount' => nil
})
end
end
end
......@@ -44,6 +44,8 @@ def pill_count
end
rescue ActiveRecord::QueryCanceled => e # rubocop:disable Database/RescueQueryCanceled -- used with fast_read_statement_timeout to prevent counts from slowing down the request
Gitlab::ErrorTracking.log_exception(e, group_id: context.group.id, query: 'group_sidebar_issues_count')
nil
end
override :pill_html_options
......
......@@ -12,6 +12,7 @@
id name path full_name full_path achievements_path description description_html visibility
lfs_enabled request_access_enabled projects root_storage_statistics shared_runners_setting
timelog_categories achievements work_item pages_deployments import_source_users work_item_types
sidebar
]
expect(described_class).to include_graphql_fields(*expected_fields)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['NamespaceSidebar'], feature_category: :navigation do
let(:fields) do
%i[open_issues_count open_merge_requests_count]
end
specify { expect(described_class.graphql_name).to eq('NamespaceSidebar') }
specify { expect(described_class).to have_graphql_fields(fields).at_least }
end
......@@ -64,7 +64,7 @@
it 'logs the error and returns a null count' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
ActiveRecord::QueryCanceled, group_id: group.id, query: 'group_sidebar_issues_count'
)
).and_call_original
expect(menu.pill_count).to be_nil
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Namespace.sidebar', feature_category: :navigation do
include GraphqlHelpers
let_it_be(:group) { create(:group, :private) }
let_it_be(:project) { create(:project, :repository, namespace: group) }
let_it_be(:reporter) { create(:user, reporter_of: group) }
let(:query) do
<<~QUERY
query {
namespace(fullPath: "#{namespace.full_path}") {
sidebar {
openIssuesCount
openMergeRequestsCount
}
}
}
QUERY
end
before_all do
create_list(:issue, 2, project: project)
create(:merge_request, source_project: project)
end
context 'with a Group' do
let(:namespace) { group }
it 'returns the group counts' do
post_graphql(query, current_user: reporter)
expect(response).to have_gitlab_http_status(:ok)
expect(graphql_data_at(:namespace, :sidebar)).to eq({
'openIssuesCount' => 2,
'openMergeRequestsCount' => 1
})
end
context 'when issue count query times out' do
before do
allow_next_instance_of(::Groups::OpenIssuesCountService) do |service|
allow(service).to receive(:count).and_raise(ActiveRecord::QueryCanceled)
end
end
it 'logs the error and returns a null issue count' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
ActiveRecord::QueryCanceled, group_id: namespace.id, query: 'group_sidebar_issues_count'
).and_call_original
post_graphql(query, current_user: reporter)
expect(response).to have_gitlab_http_status(:ok)
expect(graphql_data_at(:namespace, :sidebar)).to eq({
'openIssuesCount' => nil,
'openMergeRequestsCount' => 1
})
end
end
end
context 'with a ProjectNamespace' do
let(:namespace) { project.project_namespace }
it 'returns the project counts' do
post_graphql(query, current_user: reporter)
expect(response).to have_gitlab_http_status(:ok)
expect(graphql_data_at(:namespace, :sidebar)).to eq({
'openIssuesCount' => 2,
'openMergeRequestsCount' => 1
})
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment