Commit d8b5d166 authored by Ash McKenzie's avatar Ash McKenzie

Merge branch '232580-issue-state-counts-graphql' into 'master'

Add issuable state count type for Project graphql

Closes #232580

See merge request gitlab-org/gitlab!38263
parents 08edb3c1 dc84ba2a
# frozen_string_literal: true
module IssueResolverFields
extend ActiveSupport::Concern
prepended do
argument :iid, GraphQL::STRING_TYPE,
required: false,
description: 'IID of the issue. For example, "1"'
argument :iids, [GraphQL::STRING_TYPE],
required: false,
description: 'List of IIDs of issues. For example, [1, 2]'
argument :label_name, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Labels applied to this issue'
argument :milestone_title, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Milestone applied to this issue'
argument :assignee_username, GraphQL::STRING_TYPE,
required: false,
description: 'Username of a user assigned to the issue'
argument :assignee_id, GraphQL::STRING_TYPE,
required: false,
description: 'ID of a user assigned to the issues, "none" and "any" values supported'
argument :created_before, Types::TimeType,
required: false,
description: 'Issues created before this date'
argument :created_after, Types::TimeType,
required: false,
description: 'Issues created after this date'
argument :updated_before, Types::TimeType,
required: false,
description: 'Issues updated before this date'
argument :updated_after, Types::TimeType,
required: false,
description: 'Issues updated after this date'
argument :closed_before, Types::TimeType,
required: false,
description: 'Issues closed before this date'
argument :closed_after, Types::TimeType,
required: false,
description: 'Issues closed after this date'
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Search query for issue title or description'
argument :types, [Types::IssueTypeEnum],
as: :issue_types,
description: 'Filter issues by the given issue types',
required: false
end
def resolve(**args)
# The project could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` of the project to query for issues, so
# make sure it's loaded and not `nil` before continuing.
parent = object.respond_to?(:sync) ? object.sync : object
return Issue.none if parent.nil?
# Will need to be made group & namespace aware with
# https://gitlab.com/gitlab-org/gitlab-foss/issues/54520
args[:iids] ||= [args.delete(:iid)].compact if args[:iid]
args[:attempt_project_search_optimizations] = true if args[:search].present?
finder = IssuesFinder.new(current_user, args)
continue_issue_resolve(parent, finder, **args)
end
class_methods do
def resolver_complexity(args, child_complexity:)
complexity = super
complexity += 2 if args[:labelName]
complexity
end
end
end
# frozen_string_literal: true
module Resolvers
class IssueStatusCountsResolver < BaseResolver
prepend IssueResolverFields
type Types::IssueStatusCountsType, null: true
def continue_issue_resolve(parent, finder, **args)
Gitlab::IssuablesCountForState.new(finder, parent)
end
end
end
......@@ -2,57 +2,15 @@
module Resolvers
class IssuesResolver < BaseResolver
argument :iid, GraphQL::STRING_TYPE,
required: false,
description: 'IID of the issue. For example, "1"'
prepend IssueResolverFields
argument :iids, [GraphQL::STRING_TYPE],
required: false,
description: 'List of IIDs of issues. For example, [1, 2]'
argument :state, Types::IssuableStateEnum,
required: false,
description: 'Current state of this issue'
argument :label_name, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Labels applied to this issue'
argument :milestone_title, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Milestones applied to this issue'
argument :assignee_username, GraphQL::STRING_TYPE,
required: false,
description: 'Username of a user assigned to the issues'
argument :assignee_id, GraphQL::STRING_TYPE,
required: false,
description: 'ID of a user assigned to the issues, "none" and "any" values supported'
argument :created_before, Types::TimeType,
required: false,
description: 'Issues created before this date'
argument :created_after, Types::TimeType,
required: false,
description: 'Issues created after this date'
argument :updated_before, Types::TimeType,
required: false,
description: 'Issues updated before this date'
argument :updated_after, Types::TimeType,
required: false,
description: 'Issues updated after this date'
argument :closed_before, Types::TimeType,
required: false,
description: 'Issues closed before this date'
argument :closed_after, Types::TimeType,
required: false,
description: 'Issues closed after this date'
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Search query for issue title or description'
argument :sort, Types::IssueSortEnum,
description: 'Sort issues by this criteria',
required: false,
default_value: 'created_desc'
argument :types, [Types::IssueTypeEnum],
as: :issue_types,
description: 'Filter issues by the given issue types',
required: false
type Types::IssueType, null: true
......@@ -60,19 +18,7 @@ module Resolvers
label_priority_asc label_priority_desc
milestone_due_asc milestone_due_desc].freeze
def resolve(**args)
# The project could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` of the project to query for issues, so
# make sure it's loaded and not `nil` before continuing.
parent = object.respond_to?(:sync) ? object.sync : object
return Issue.none if parent.nil?
# Will need to be be made group & namespace aware with
# https://gitlab.com/gitlab-org/gitlab-foss/issues/54520
args[:iids] ||= [args.delete(:iid)].compact if args[:iid]
args[:attempt_project_search_optimizations] = true if args[:search].present?
finder = IssuesFinder.new(current_user, args)
def continue_issue_resolve(parent, finder, **args)
issues = Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all
if non_stable_cursor_sort?(args[:sort])
......@@ -84,13 +30,6 @@ module Resolvers
end
end
def self.resolver_complexity(args, child_complexity:)
complexity = super
complexity += 2 if args[:labelName]
complexity
end
def non_stable_cursor_sort?(sort)
NON_STABLE_CURSOR_SORTS.include?(sort)
end
......
# frozen_string_literal: true
module Types
class IssueStatusCountsType < BaseObject
graphql_name 'IssueStatusCountsType'
description "Represents total number of issues for the represented statuses."
authorize :read_issue
def self.available_issue_states
@available_issue_states ||= Issue.available_states.keys.push('all')
end
::Gitlab::IssuablesCountForState::STATES.each do |state|
next unless available_issue_states.include?(state.downcase)
field state,
GraphQL::INT_TYPE,
null: true,
description: "Number of issues with status #{state.upcase} for the project"
end
end
end
......@@ -148,6 +148,12 @@ module Types
description: 'Issues of the project',
resolver: Resolvers::IssuesResolver
field :issue_status_counts,
Types::IssueStatusCountsType,
null: true,
description: 'Counts of issues by status for the project',
resolver: Resolvers::IssueStatusCountsResolver
field :milestones, Types::MilestoneType.connection_type, null: true,
description: 'Milestones of the project',
resolver: Resolvers::ProjectMilestonesResolver
......@@ -164,8 +170,8 @@ module Types
resolver: Resolvers::EnvironmentsResolver
field :sast_ci_configuration, ::Types::CiConfiguration::Sast::Type, null: true,
description: 'SAST CI configuration for the project',
resolver: ::Resolvers::CiConfiguration::SastResolver
description: 'SAST CI configuration for the project',
resolver: ::Resolvers::CiConfiguration::SastResolver
field :issue,
Types::IssueType,
......
---
title: Add issue status counts to Projects in GraphQL
merge_request: 38263
author:
type: added
......@@ -5889,7 +5889,7 @@ type Group {
assigneeId: String
"""
Username of a user assigned to the issues
Username of a user assigned to the issue
"""
assigneeUsername: String
......@@ -5949,7 +5949,7 @@ type Group {
last: Int
"""
Milestones applied to this issue
Milestone applied to this issue
"""
milestoneTitle: [String]
......@@ -7535,6 +7535,26 @@ enum IssueState {
opened
}
"""
Represents total number of issues for the represented statuses.
"""
type IssueStatusCountsType {
"""
Number of issues with status ALL for the project
"""
all: Int
"""
Number of issues with status CLOSED for the project
"""
closed: Int
"""
Number of issues with status OPENED for the project
"""
opened: Int
}
"""
Issue type
"""
......@@ -10501,7 +10521,7 @@ type Project {
assigneeId: String
"""
Username of a user assigned to the issues
Username of a user assigned to the issue
"""
assigneeUsername: String
......@@ -10546,7 +10566,7 @@ type Project {
labelName: [String]
"""
Milestones applied to this issue
Milestone applied to this issue
"""
milestoneTitle: [String]
......@@ -10581,6 +10601,81 @@ type Project {
updatedBefore: Time
): Issue
"""
Counts of issues by status for the project
"""
issueStatusCounts(
"""
ID of a user assigned to the issues, "none" and "any" values supported
"""
assigneeId: String
"""
Username of a user assigned to the issue
"""
assigneeUsername: String
"""
Issues closed after this date
"""
closedAfter: Time
"""
Issues closed before this date
"""
closedBefore: Time
"""
Issues created after this date
"""
createdAfter: Time
"""
Issues created before this date
"""
createdBefore: Time
"""
IID of the issue. For example, "1"
"""
iid: String
"""
List of IIDs of issues. For example, [1, 2]
"""
iids: [String!]
"""
Labels applied to this issue
"""
labelName: [String]
"""
Milestone applied to this issue
"""
milestoneTitle: [String]
"""
Search query for issue title or description
"""
search: String
"""
Filter issues by the given issue types
"""
types: [IssueType!]
"""
Issues updated after this date
"""
updatedAfter: Time
"""
Issues updated before this date
"""
updatedBefore: Time
): IssueStatusCountsType
"""
Issues of the project
"""
......@@ -10596,7 +10691,7 @@ type Project {
assigneeId: String
"""
Username of a user assigned to the issues
Username of a user assigned to the issue
"""
assigneeUsername: String
......@@ -10656,7 +10751,7 @@ type Project {
last: Int
"""
Milestones applied to this issue
Milestone applied to this issue
"""
milestoneTitle: [String]
......
......@@ -16365,16 +16365,6 @@
},
"defaultValue": null
},
{
"name": "state",
"description": "Current state of this issue",
"type": {
"kind": "ENUM",
"name": "IssuableState",
"ofType": null
},
"defaultValue": null
},
{
"name": "labelName",
"description": "Labels applied to this issue",
......@@ -16391,7 +16381,7 @@
},
{
"name": "milestoneTitle",
"description": "Milestones applied to this issue",
"description": "Milestone applied to this issue",
"type": {
"kind": "LIST",
"name": null,
......@@ -16405,7 +16395,7 @@
},
{
"name": "assigneeUsername",
"description": "Username of a user assigned to the issues",
"description": "Username of a user assigned to the issue",
"type": {
"kind": "SCALAR",
"name": "String",
......@@ -16493,16 +16483,6 @@
},
"defaultValue": null
},
{
"name": "sort",
"description": "Sort issues by this criteria",
"type": {
"kind": "ENUM",
"name": "IssueSort",
"ofType": null
},
"defaultValue": "created_desc"
},
{
"name": "types",
"description": "Filter issues by the given issue types",
......@@ -16521,6 +16501,26 @@
},
"defaultValue": null
},
{
"name": "state",
"description": "Current state of this issue",
"type": {
"kind": "ENUM",
"name": "IssuableState",
"ofType": null
},
"defaultValue": null
},
{
"name": "sort",
"description": "Sort issues by this criteria",
"type": {
"kind": "ENUM",
"name": "IssueSort",
"ofType": null
},
"defaultValue": "created_desc"
},
{
"name": "iterationId",
"description": "Iterations applied to the issue",
......@@ -20767,6 +20767,61 @@
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "IssueStatusCountsType",
"description": "Represents total number of issues for the represented statuses.",
"fields": [
{
"name": "all",
"description": "Number of issues with status ALL for the project",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "closed",
"description": "Number of issues with status CLOSED for the project",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "opened",
"description": "Number of issues with status OPENED for the project",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "IssueType",
......@@ -31279,16 +31334,6 @@
},
"defaultValue": null
},
{
"name": "state",
"description": "Current state of this issue",
"type": {
"kind": "ENUM",
"name": "IssuableState",
"ofType": null
},
"defaultValue": null
},
{
"name": "labelName",
"description": "Labels applied to this issue",
......@@ -31305,7 +31350,7 @@
},
{
"name": "milestoneTitle",
"description": "Milestones applied to this issue",
"description": "Milestone applied to this issue",
"type": {
"kind": "LIST",
"name": null,
......@@ -31319,7 +31364,7 @@
},
{
"name": "assigneeUsername",
"description": "Username of a user assigned to the issues",
"description": "Username of a user assigned to the issue",
"type": {
"kind": "SCALAR",
"name": "String",
......@@ -31407,16 +31452,6 @@
},
"defaultValue": null
},
{
"name": "sort",
"description": "Sort issues by this criteria",
"type": {
"kind": "ENUM",
"name": "IssueSort",
"ofType": null
},
"defaultValue": "created_desc"
},
{
"name": "types",
"description": "Filter issues by the given issue types",
......@@ -31435,6 +31470,26 @@
},
"defaultValue": null
},
{
"name": "state",
"description": "Current state of this issue",
"type": {
"kind": "ENUM",
"name": "IssuableState",
"ofType": null
},
"defaultValue": null
},
{
"name": "sort",
"description": "Sort issues by this criteria",
"type": {
"kind": "ENUM",
"name": "IssueSort",
"ofType": null
},
"defaultValue": "created_desc"
},
{
"name": "iterationId",
"description": "Iterations applied to the issue",
......@@ -31459,8 +31514,8 @@
"deprecationReason": null
},
{
"name": "issues",
"description": "Issues of the project",
"name": "issueStatusCounts",
"description": "Counts of issues by status for the project",
"args": [
{
"name": "iid",
......@@ -31491,15 +31546,182 @@
"defaultValue": null
},
{
"name": "state",
"description": "Current state of this issue",
"name": "labelName",
"description": "Labels applied to this issue",
"type": {
"kind": "ENUM",
"name": "IssuableState",
"kind": "LIST",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "milestoneTitle",
"description": "Milestone applied to this issue",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "assigneeUsername",
"description": "Username of a user assigned to the issue",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "assigneeId",
"description": "ID of a user assigned to the issues, \"none\" and \"any\" values supported",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "createdBefore",
"description": "Issues created before this date",
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},
{
"name": "createdAfter",
"description": "Issues created after this date",
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},
{
"name": "updatedBefore",
"description": "Issues updated before this date",
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},
{
"name": "updatedAfter",
"description": "Issues updated after this date",
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},
{
"name": "closedBefore",
"description": "Issues closed before this date",
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},
{
"name": "closedAfter",
"description": "Issues closed after this date",
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},
{
"name": "search",
"description": "Search query for issue title or description",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "types",
"description": "Filter issues by the given issue types",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "IssueType",
"ofType": null
}
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "IssueStatusCountsType",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issues",
"description": "Issues of the project",
"args": [
{
"name": "iid",
"description": "IID of the issue. For example, \"1\"",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "iids",
"description": "List of IIDs of issues. For example, [1, 2]",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "labelName",
"description": "Labels applied to this issue",
......@@ -31516,7 +31738,7 @@
},
{
"name": "milestoneTitle",
"description": "Milestones applied to this issue",
"description": "Milestone applied to this issue",
"type": {
"kind": "LIST",
"name": null,
......@@ -31530,7 +31752,7 @@
},
{
"name": "assigneeUsername",
"description": "Username of a user assigned to the issues",
"description": "Username of a user assigned to the issue",
"type": {
"kind": "SCALAR",
"name": "String",
......@@ -31618,16 +31840,6 @@
},
"defaultValue": null
},
{
"name": "sort",
"description": "Sort issues by this criteria",
"type": {
"kind": "ENUM",
"name": "IssueSort",
"ofType": null
},
"defaultValue": "created_desc"
},
{
"name": "types",
"description": "Filter issues by the given issue types",
......@@ -31646,6 +31858,26 @@
},
"defaultValue": null
},
{
"name": "state",
"description": "Current state of this issue",
"type": {
"kind": "ENUM",
"name": "IssuableState",
"ofType": null
},
"defaultValue": null
},
{
"name": "sort",
"description": "Sort issues by this criteria",
"type": {
"kind": "ENUM",
"name": "IssueSort",
"ofType": null
},
"defaultValue": "created_desc"
},
{
"name": "iterationId",
"description": "Iterations applied to the issue",
......@@ -1124,6 +1124,16 @@ Autogenerated return type of IssueSetWeight
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation |
## IssueStatusCountsType
Represents total number of issues for the represented statuses.
| Name | Type | Description |
| --- | ---- | ---------- |
| `all` | Int | Number of issues with status ALL for the project |
| `closed` | Int | Number of issues with status CLOSED for the project |
| `opened` | Int | Number of issues with status OPENED for the project |
## Iteration
Represents an iteration object.
......@@ -1580,6 +1590,7 @@ Information about pagination in a connection.
| `id` | ID! | ID of the project |
| `importStatus` | String | Status of import background job of the project |
| `issue` | Issue | A single issue of the project |
| `issueStatusCounts` | IssueStatusCountsType | Counts of issues by status for the project |
| `issuesEnabled` | Boolean | Indicates if Issues are enabled for the current user |
| `jiraImportStatus` | String | Status of Jira import background job of the project |
| `jobsEnabled` | Boolean | Indicates if CI/CD pipeline jobs are enabled for the current user |
......
......@@ -9,9 +9,16 @@ module Gitlab
# The state values that can be safely casted to a Symbol.
STATES = %w[opened closed merged all].freeze
attr_reader :project
def self.declarative_policy_class
'IssuablePolicy'
end
# finder - The finder class to use for retrieving the issuables.
def initialize(finder)
def initialize(finder, project = nil)
@finder = finder
@project = project
@cache = Gitlab::SafeRequestStore[CACHE_KEY] ||= initialize_cache
end
......@@ -19,6 +26,11 @@ module Gitlab
self[state || :opened]
end
# Define method for each state
STATES.each do |state|
define_method(state) { self[state] }
end
# Returns the count for the given state.
#
# state - The name of the state as either a String or a Symbol.
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::IssueStatusCountsResolver do
include GraphqlHelpers
describe '#resolve' do
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) }
let_it_be(:incident) { create(:incident, project: project, state: :closed, created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) }
let(:args) { {} }
before do
project.add_developer(current_user)
end
subject { resolve_issue_status_counts(args) }
it { is_expected.to be_a(Gitlab::IssuablesCountForState) }
specify { expect(subject.project).to eq(project) }
it 'returns expected results' do
result = resolve_issue_status_counts
expect(result.all).to eq 2
expect(result.opened).to eq 1
expect(result.closed).to eq 1
end
it 'filters by search', :aggregate_failures do
result = resolve_issue_status_counts(search: issue.title)
expect(result.all).to eq 1
expect(result.opened).to eq 1
expect(result.closed).to eq 0
end
it 'filters by issue type', :aggregate_failures do
result = resolve_issue_status_counts(issue_types: ['incident'])
expect(result.all).to eq 1
expect(result.opened).to eq 0
expect(result.closed).to eq 1
end
# The state param is ignored in IssuableFinder#count_by_state
it 'ignores state filter', :aggregate_failures do
result = resolve_issue_status_counts(state: 'closed')
expect(result.all).to eq 2
expect(result.opened).to eq 1
expect(result.closed).to eq 1
end
private
def resolve_issue_status_counts(args = {}, context = { current_user: current_user })
resolve(described_class, obj: project, args: args, ctx: context)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['IssueStatusCountsType'] do
specify { expect(described_class.graphql_name).to eq('IssueStatusCountsType') }
it 'exposes the expected fields' do
expected_fields = %i[
all
opened
closed
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end
......@@ -27,6 +27,7 @@ RSpec.describe GitlabSchema.types['Project'] do
boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts
container_expiration_policy sast_ci_configuration service_desk_enabled service_desk_address
issue_status_counts
]
expect(described_class).to include_graphql_fields(*expected_fields)
......
......@@ -9,6 +9,21 @@ RSpec.describe Gitlab::IssuablesCountForState do
let(:counter) { described_class.new(finder) }
describe 'project given' do
let(:project) { build(:project) }
let(:counter) { described_class.new(finder, project) }
it 'provides the project' do
expect(counter.project).to eq(project)
end
end
describe '.declarative_policy_class' do
subject { described_class.declarative_policy_class }
it { is_expected.to eq('IssuablePolicy') }
end
describe '#for_state_or_opened' do
it 'returns the number of issuables for the given state' do
expect(counter.for_state_or_opened(:closed)).to eq(1)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting Issue counts by status' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:current_user) { create(:user) }
let_it_be(:issue_opened) { create(:issue, project: project) }
let_it_be(:issue_closed) { create(:issue, :closed, project: project) }
let_it_be(:other_project_issue) { create(:issue) }
let(:params) { {} }
let(:fields) do
<<~QUERY
#{all_graphql_fields_for('IssueStatusCountsType'.classify)}
QUERY
end
let(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
query_graphql_field('issueStatusCounts', params, fields)
)
end
context 'with issue count data' do
let(:issue_counts) { graphql_data.dig('project', 'issueStatusCounts') }
context 'without project permissions' do
let(:user) { create(:user) }
before do
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
it { expect(issue_counts).to be nil }
end
context 'with project permissions' do
before do
project.add_developer(current_user)
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
it 'returns the correct counts for each status' do
expect(issue_counts).to eq(
'all' => 2,
'opened' => 1,
'closed' => 1
)
end
end
end
end
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