Skip to content
代码片段 群组 项目
提交 f65dddac 编辑于 作者: Eugenia Grieff's avatar Eugenia Grieff 提交者: Nicolas Dular
浏览文件

Add support for querying work items counts by state

Add field workItemStateCounts to project and group query

Changelog: added
上级 d08e6622
No related branches found
No related tags found
无相关合并请求
显示 409 个添加8 个删除
# frozen_string_literal: true
module Resolvers
module Namespaces
class WorkItemStateCountsResolver < WorkItemsResolver
type Types::WorkItemStateCountsType, null: true
def ready?(**args)
# The search filter is not supported for work times at the namespace level.
# See https://gitlab.com/gitlab-org/gitlab/-/work_items/393126
if args[:search]
raise Gitlab::Graphql::Errors::ArgumentError,
'Searching is not available for work items at the namespace level yet'
end
super
end
def resolve(**args)
return if resource_parent.nil?
Gitlab::IssuablesCountForState.new(
finder(args),
resource_parent,
store_in_redis_cache: true
)
end
end
end
end
# frozen_string_literal: true
module Resolvers
class WorkItemStateCountsResolver < WorkItemsResolver
type Types::WorkItemStateCountsType, null: true
def resolve(**args)
return if resource_parent.nil?
work_item_finder = finder(prepare_finder_params(args))
work_item_finder.parent_param = resource_parent
Gitlab::IssuablesCountForState.new(work_item_finder, resource_parent)
end
end
end
......@@ -275,6 +275,14 @@ class GroupType < NamespaceType
description: 'Find a work item by IID directly associated with the group. Returns `null` if the ' \
'`namespace_level_work_items` feature flag is disabled.'
field :work_item_state_counts,
Types::WorkItemStateCountsType,
null: true,
alpha: { milestone: '16.7' },
description: 'Counts of work items by state for the namespace. Returns `null` if the ' \
'`namespace_level_work_items` feature flag is disabled.',
resolver: Resolvers::Namespaces::WorkItemStateCountsResolver
field :autocomplete_users,
null: true,
resolver: Resolvers::AutocompleteUsersResolver,
......
......@@ -254,6 +254,13 @@ class ProjectType < BaseObject
extras: [:lookahead],
resolver: Resolvers::WorkItemsResolver
field :work_item_state_counts,
Types::WorkItemStateCountsType,
null: true,
alpha: { milestone: '16.7' },
description: 'Counts of work items by state for the project.',
resolver: Resolvers::WorkItemStateCountsResolver
field :issue_status_counts,
Types::IssueStatusCountsType,
null: true,
......
# frozen_string_literal: true
module Types
# rubocop: disable Graphql/AuthorizeTypes -- Parent node applies authorization
class WorkItemStateCountsType < BaseObject
graphql_name 'WorkItemStateCountsType'
description 'Represents total number of work items for the represented states'
field :all,
GraphQL::Types::Int,
null: true,
description: 'Number of work items for the project or group.'
field :closed,
GraphQL::Types::Int,
null: true,
description: 'Number of work items with state CLOSED for the project or group.'
field :opened,
GraphQL::Types::Int,
null: true,
description: 'Number of work items with state OPENED for the project or group.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
......@@ -20219,6 +20219,31 @@ Returns [`WorkItem`](#workitem).
| ---- | ---- | ----------- |
| <a id="groupworkitemiid"></a>`iid` | [`String!`](#string) | IID of the work item. |
 
##### `Group.workItemStateCounts`
Counts of work items by state for the namespace. Returns `null` if the `namespace_level_work_items` feature flag is disabled.
WARNING:
**Introduced** in 16.7.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`WorkItemStateCountsType`](#workitemstatecountstype).
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="groupworkitemstatecountsauthorusername"></a>`authorUsername` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.9. This feature is an Experiment. It can be changed or removed at any time. Filter work items by author username. |
| <a id="groupworkitemstatecountsiid"></a>`iid` | [`String`](#string) | IID of the work item. For example, "1". |
| <a id="groupworkitemstatecountsiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of work items. For example, `["1", "2"]`. |
| <a id="groupworkitemstatecountsin"></a>`in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
| <a id="groupworkitemstatecountsrequirementlegacywidget"></a>`requirementLegacyWidget` **{warning-solid}** | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in 15.9. Use work item IID filter instead. |
| <a id="groupworkitemstatecountssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="groupworkitemstatecountssort"></a>`sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. |
| <a id="groupworkitemstatecountsstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of the work item. |
| <a id="groupworkitemstatecountsstatuswidget"></a>`statusWidget` | [`StatusFilterInput`](#statusfilterinput) | Input for status widget filter. Ignored if `work_items_mvc_2` is disabled. |
| <a id="groupworkitemstatecountstypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter work items by the given work item types. |
##### `Group.workItemTypes`
 
Work item types available to the group.
......@@ -25499,6 +25524,31 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| <a id="projectvulnerabilityseveritiescountseverity"></a>`severity` | [`[VulnerabilitySeverity!]`](#vulnerabilityseverity) | Filter vulnerabilities by severity. |
| <a id="projectvulnerabilityseveritiescountstate"></a>`state` | [`[VulnerabilityState!]`](#vulnerabilitystate) | Filter vulnerabilities by state. |
 
##### `Project.workItemStateCounts`
Counts of work items by state for the project.
WARNING:
**Introduced** in 16.7.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`WorkItemStateCountsType`](#workitemstatecountstype).
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectworkitemstatecountsauthorusername"></a>`authorUsername` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.9. This feature is an Experiment. It can be changed or removed at any time. Filter work items by author username. |
| <a id="projectworkitemstatecountsiid"></a>`iid` | [`String`](#string) | IID of the work item. For example, "1". |
| <a id="projectworkitemstatecountsiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of work items. For example, `["1", "2"]`. |
| <a id="projectworkitemstatecountsin"></a>`in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
| <a id="projectworkitemstatecountsrequirementlegacywidget"></a>`requirementLegacyWidget` **{warning-solid}** | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in 15.9. Use work item IID filter instead. |
| <a id="projectworkitemstatecountssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="projectworkitemstatecountssort"></a>`sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. |
| <a id="projectworkitemstatecountsstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of the work item. |
| <a id="projectworkitemstatecountsstatuswidget"></a>`statusWidget` | [`StatusFilterInput`](#statusfilterinput) | Input for status widget filter. Ignored if `work_items_mvc_2` is disabled. |
| <a id="projectworkitemstatecountstypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter work items by the given work item types. |
##### `Project.workItemTypes`
 
Work item types available to the project.
......@@ -28596,6 +28646,18 @@ Check permissions for the current user on a work item.
| <a id="workitempermissionssetworkitemmetadata"></a>`setWorkItemMetadata` | [`Boolean!`](#boolean) | If `true`, the user can perform `set_work_item_metadata` on this resource. |
| <a id="workitempermissionsupdateworkitem"></a>`updateWorkItem` | [`Boolean!`](#boolean) | If `true`, the user can perform `update_work_item` on this resource. |
 
### `WorkItemStateCountsType`
Represents total number of work items for the represented states.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemstatecountstypeall"></a>`all` | [`Int`](#int) | Number of work items for the project or group. |
| <a id="workitemstatecountstypeclosed"></a>`closed` | [`Int`](#int) | Number of work items with state CLOSED for the project or group. |
| <a id="workitemstatecountstypeopened"></a>`opened` | [`Int`](#int) | Number of work items with state OPENED for the project or group. |
### `WorkItemType`
 
#### Fields
......@@ -124,7 +124,7 @@ def fast_count_by_state_failure!
def cache_issues_count?
@store_in_redis_cache &&
finder.instance_of?(IssuesFinder) &&
finder.class <= IssuesFinder &&
parent_group.present? &&
!params_include_filters?
end
......@@ -134,7 +134,7 @@ def parent_group
end
def redis_cache_key
['group', parent_group&.id, 'issues']
['group', parent_group&.id, finder.klass.model_name.plural]
end
def cache_options
......@@ -143,8 +143,8 @@ def cache_options
def params_include_filters?
non_filtering_params = %i[
scope state sort group_id include_subgroups
attempt_group_search_optimizations non_archived issue_types
scope state sort group_id include_subgroups namespace_id
attempt_group_search_optimizations non_archived issue_types lookahead
]
finder.params.except(*non_filtering_params).values.any?
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['WorkItemStateCountsType'], feature_category: :portfolio_management do
specify { expect(described_class.graphql_name).to eq('WorkItemStateCountsType') }
it 'exposes the expected fields' do
expected_fields = %i[all opened closed]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end
......@@ -72,7 +72,6 @@
let_it_be(:group) { create(:group) }
let(:cache_options) { { expires_in: 1.hour } }
let(:cache_key) { ['group', group.id, 'issues'] }
let(:threshold) { described_class::THRESHOLD }
let(:states_count) { { opened: 1, closed: 1, all: 2 } }
let(:params) { {} }
......@@ -95,9 +94,7 @@
end
end
context 'with Issues' do
let(:finder) { IssuesFinder.new(user, params) }
shared_examples 'calculating counts for issuables' do
it 'returns -1 for the requested state' do
allow(finder).to receive(:count_by_state).and_raise(ActiveRecord::QueryCanceled)
expect(Rails.cache).not_to receive(:write)
......@@ -162,6 +159,20 @@
end
end
context 'with Issues' do
let(:finder) { IssuesFinder.new(user, params) }
let(:cache_key) { ['group', group.id, 'issues'] }
it_behaves_like 'calculating counts for issuables'
end
context 'with Work Items' do
let(:finder) { ::WorkItems::WorkItemsFinder.new(user, params) }
let(:cache_key) { ['group', group.id, 'work_items'] }
it_behaves_like 'calculating counts for issuables'
end
context 'with Merge Requests' do
let(:finder) { MergeRequestsFinder.new(user, params) }
......
# frozen_string_literal: true
require 'spec_helper'
require 'request_store'
RSpec.describe 'getting Work Item counts by state', feature_category: :portfolio_management do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
let_it_be(:work_item_opened1) { create(:work_item, namespace: group) }
let_it_be(:work_item_opened2) { create(:work_item, namespace: group, author: current_user) }
let_it_be(:work_item_closed1) { create(:work_item, :closed, namespace: group) }
let_it_be(:work_item_closed2) { create(:work_item, :closed, namespace: group) }
let(:params) { {} }
subject(:query_counts) { post_graphql(query, current_user: current_user) }
context 'with work items count data' do
let(:work_item_counts) { graphql_data.dig('group', 'workItemStateCounts') }
context 'with group permissions' do
before_all do
group.add_developer(current_user)
end
it_behaves_like 'a working graphql query' do
before do
query_counts
end
end
it 'returns the correct counts for each state' do
query_counts
expect(work_item_counts).to eq(
'all' => 4,
'opened' => 2,
'closed' => 2
)
end
context 'when filters are provided' do
context 'when filtering by author username' do
let(:params) { { 'authorUsername' => current_user.username } }
it 'returns the correct counts for each state' do
query_counts
expect(work_item_counts).to eq(
'all' => 1,
'opened' => 1,
'closed' => 0
)
end
end
context 'when filtering by search' do
let(:params) { { search: 'foo', in: [:TITLE] } }
it 'returns an error for filters that are not supported' do
query_counts
expect(graphql_errors).to contain_exactly(
hash_including('message' => 'Searching is not available for work items at the namespace level yet')
)
end
end
end
context 'when the namespace_level_work_items feature flag is disabled' do
before do
stub_feature_flags(namespace_level_work_items: false)
end
it 'does not return work item counts' do
query_counts
expect_graphql_errors_to_be_empty
expect(work_item_counts).to be_nil
end
end
end
context 'without group permissions' do
it 'does not return work item counts' do
query_counts
expect_graphql_errors_to_be_empty
expect(work_item_counts).to be_nil
end
end
end
def query(args: params)
fields = <<~QUERY
#{all_graphql_fields_for('WorkItemStateCountsType'.classify)}
QUERY
graphql_query_for(
'group',
{ 'fullPath' => group.full_path },
query_graphql_field('workItemStateCounts', args, fields)
)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting Work Item counts by state', feature_category: :portfolio_management do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
let_it_be(:project) { create(:project, :repository, :private, group: group) }
let_it_be(:work_item_opened1) { create(:work_item, project: project, title: 'Foo') }
let_it_be(:work_item_opened2) { create(:work_item, project: project, author: current_user) }
let_it_be(:work_item_closed) { create(:work_item, :closed, project: project, description: 'Bar') }
let(:params) { {} }
subject(:query_counts) { post_graphql(query, current_user: current_user) }
context 'with work items count data' do
let(:work_item_counts) { graphql_data.dig('project', 'workItemStateCounts') }
context 'with project permissions' do
before_all do
group.add_developer(current_user)
end
it_behaves_like 'a working graphql query' do
before do
query_counts
end
end
it 'returns the correct counts for each state' do
query_counts
expect(work_item_counts).to eq(
'all' => 3,
'opened' => 2,
'closed' => 1
)
end
context 'when other work items are present in the group' do
it 'only returns counts for work items in the current project' do
other_project = create(:project, :repository, group: group)
create(:work_item, project: other_project)
query_counts
expect(work_item_counts).to eq(
'all' => 3,
'opened' => 2,
'closed' => 1
)
end
end
context 'when filters are provided' do
context 'when filtering by author username' do
let(:params) { { 'authorUsername' => current_user.username } }
it 'returns the correct counts for each status' do
query_counts
expect(work_item_counts).to eq(
'all' => 1,
'opened' => 1,
'closed' => 0
)
end
end
context 'when searching in title' do
let(:params) { { search: 'Foo', in: [:TITLE] } }
it 'returns the correct counts for each status' do
query_counts
expect(work_item_counts).to eq(
'all' => 1,
'opened' => 1,
'closed' => 0
)
end
end
context 'when searching in description' do
let(:params) { { search: 'Bar', in: [:DESCRIPTION] } }
it 'returns the correct counts for each status' do
query_counts
expect(work_item_counts).to eq(
'all' => 1,
'opened' => 0,
'closed' => 1
)
end
end
end
end
context 'without project permissions' do
it 'does not return work item counts' do
query_counts
expect_graphql_errors_to_be_empty
expect(work_item_counts).to be_nil
end
end
end
def query(args: params)
fields = <<~QUERY
#{all_graphql_fields_for('WorkItemStateCountsType'.classify)}
QUERY
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
query_graphql_field('workItemStateCounts', args, fields)
)
end
end
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册