Commit dd43ba1e authored by Sean Arnold's avatar Sean Arnold Committed by Mayra Cabrera

Add issue_type filter for issues in graphql

- Add type
- Add resolver
- Add scope to model
parent 98a5301e
......@@ -25,6 +25,7 @@
# updated_after: datetime
# updated_before: datetime
# confidential: boolean
# issue_type: array of strings (one of Issue.issue_types)
#
class IssuesFinder < IssuableFinder
CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
......@@ -73,6 +74,7 @@ class IssuesFinder < IssuableFinder
issues = super
issues = by_due_date(issues)
issues = by_confidential(issues)
issues = by_issue_types(issues)
issues
end
......@@ -97,6 +99,14 @@ class IssuesFinder < IssuableFinder
items.due_between(Date.today - 2.weeks, (Date.today + 1.month).end_of_month)
end
end
def by_issue_types(items)
issue_type_params = Array(params[:issue_types]).map(&:to_s)
return items if issue_type_params.blank?
return Issue.none unless (Issue.issue_types.keys & issue_type_params).sort == issue_type_params.sort
items.with_issue_type(params[:issue_types])
end
end
IssuesFinder.prepend_if_ee('EE::IssuesFinder')
......@@ -49,6 +49,10 @@ module Resolvers
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
......
......@@ -97,6 +97,10 @@ module Types
field :design_collection, Types::DesignManagement::DesignCollectionType, null: true,
description: 'Collection of design images associated with this issue'
field :type, Types::IssueTypeEnum, null: true,
method: :issue_type,
description: 'Type of the issue'
end
end
......
# frozen_string_literal: true
module Types
class IssueTypeEnum < BaseEnum
graphql_name 'IssueType'
description 'Issue type'
::Issue.issue_types.keys.each do |issue_type|
value issue_type.upcase, value: issue_type, description: "#{issue_type.titleize} issue type"
end
end
end
......@@ -106,6 +106,7 @@ class Issue < ApplicationRecord
milestone: { project: [:route, { namespace: :route }] },
project: [:route, { namespace: :route }])
}
scope :with_issue_type, ->(types) { where(issue_type: types) }
scope :public_only, -> { where(confidential: false) }
scope :confidential_only, -> { where(confidential: true) }
......
---
title: Filter Issues in GraphQL by type of Issue
merge_request: 38017
author:
type: added
......@@ -4505,6 +4505,11 @@ type EpicIssue implements Noteable {
"""
totalTimeSpent: Int!
"""
Type of the issue
"""
type: IssueType
"""
Timestamp of when the issue was last updated
"""
......@@ -5263,6 +5268,11 @@ type Group {
"""
state: IssuableState
"""
Filter issues by the given issue types
"""
types: [IssueType!]
"""
Issues updated after this date
"""
......@@ -6138,6 +6148,11 @@ type Issue implements Noteable {
"""
totalTimeSpent: Int!
"""
Type of the issue
"""
type: IssueType
"""
Timestamp of when the issue was last updated
"""
......@@ -6669,6 +6684,21 @@ enum IssueState {
opened
}
"""
Issue type
"""
enum IssueType {
"""
Incident issue type
"""
INCIDENT
"""
Issue issue type
"""
ISSUE
}
"""
Represents an iteration object.
"""
......@@ -9593,6 +9623,11 @@ type Project {
"""
state: IssuableState
"""
Filter issues by the given issue types
"""
types: [IssueType!]
"""
Issues updated after this date
"""
......@@ -9698,6 +9733,11 @@ type Project {
"""
state: IssuableState
"""
Filter issues by the given issue types
"""
types: [IssueType!]
"""
Issues updated after this date
"""
......
......@@ -12575,6 +12575,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "type",
"description": "Type of the issue",
"args": [
],
"type": {
"kind": "ENUM",
"name": "IssueType",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updatedAt",
"description": "Timestamp of when the issue was last updated",
......@@ -14575,6 +14589,24 @@
},
"defaultValue": "created_desc"
},
{
"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
},
{
"name": "iterationId",
"description": "Iterations applied to the issue",
......@@ -16931,6 +16963,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "type",
"description": "Type of the issue",
"args": [
],
"type": {
"kind": "ENUM",
"name": "IssueType",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updatedAt",
"description": "Timestamp of when the issue was last updated",
......@@ -18412,6 +18458,29 @@
],
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "IssueType",
"description": "Issue type",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "ISSUE",
"description": "Issue issue type",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "INCIDENT",
"description": "Incident issue type",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Iteration",
......@@ -28690,6 +28759,24 @@
},
"defaultValue": "created_desc"
},
{
"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
},
{
"name": "iterationId",
"description": "Iterations applied to the issue",
......@@ -28883,6 +28970,24 @@
},
"defaultValue": "created_desc"
},
{
"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
},
{
"name": "iterationId",
"description": "Iterations applied to the issue",
......@@ -756,6 +756,7 @@ Relationship between an epic and an issue
| `title` | String! | Title of the issue |
| `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` |
| `totalTimeSpent` | Int! | Total time reported as spent on the issue |
| `type` | IssueType | Type of the issue |
| `updatedAt` | Time! | Timestamp of when the issue was last updated |
| `upvotes` | Int! | Number of upvotes the issue has received |
| `userNotesCount` | Int! | Number of user notes of the issue |
......@@ -922,6 +923,7 @@ Represents a Group Member
| `title` | String! | Title of the issue |
| `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` |
| `totalTimeSpent` | Int! | Total time reported as spent on the issue |
| `type` | IssueType | Type of the issue |
| `updatedAt` | Time! | Timestamp of when the issue was last updated |
| `upvotes` | Int! | Number of upvotes the issue has received |
| `userNotesCount` | Int! | Number of user notes of the issue |
......
......@@ -668,6 +668,58 @@ RSpec.describe IssuesFinder do
end
end
context 'filtering by issue type' do
let_it_be(:incident_issue) { create(:incident, project: project1) }
context 'no type given' do
let(:params) { { issue_types: [] } }
it 'returns all issues' do
expect(issues).to contain_exactly(incident_issue, issue1, issue2, issue3, issue4)
end
end
context 'incident type' do
let(:params) { { issue_types: ['incident'] } }
it 'returns incident issues' do
expect(issues).to contain_exactly(incident_issue)
end
end
context 'issue type' do
let(:params) { { issue_types: ['issue'] } }
it 'returns all issues with type issue' do
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
end
end
context 'multiple params' do
let(:params) { { issue_types: %w(issue incident) } }
it 'returns all issues' do
expect(issues).to contain_exactly(incident_issue, issue1, issue2, issue3, issue4)
end
end
context 'without array' do
let(:params) { { issue_types: 'incident' } }
it 'returns incident issues' do
expect(issues).to contain_exactly(incident_issue)
end
end
context 'invalid params' do
let(:params) { { issue_types: ['nonsense'] } }
it 'returns no issues' do
expect(issues).to eq(Issue.none)
end
end
end
context 'when the user is unauthorized' do
let(:search_user) { nil }
......
......@@ -13,7 +13,7 @@ RSpec.describe Resolvers::IssuesResolver do
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:assignee) { create(:user) }
let_it_be(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago, milestone: milestone) }
let_it_be(:issue1) { create(:incident, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago, milestone: milestone) }
let_it_be(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago, assignees: [assignee]) }
let_it_be(:issue3) { create(:issue, project: other_project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago, assignees: [assignee]) }
let_it_be(:issue4) { create(:issue) }
......@@ -95,6 +95,20 @@ RSpec.describe Resolvers::IssuesResolver do
end
end
describe 'filters by issue_type' do
it 'filters by a single type' do
expect(resolve_issues(issue_types: ['incident'])).to contain_exactly(issue1)
end
it 'filters by more than one type' do
expect(resolve_issues(issue_types: %w(incident issue))).to contain_exactly(issue1, issue2)
end
it 'ignores the filter if none given' do
expect(resolve_issues(issue_types: [])).to contain_exactly(issue1, issue2)
end
end
context 'when searching issues' do
it 'returns correct issues' do
expect(resolve_issues(search: 'foo')).to contain_exactly(issue2)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::IssueTypeEnum do
specify { expect(described_class.graphql_name).to eq('IssueType') }
it 'exposes all the existing issue type values' do
expect(described_class.values.keys).to include(
*%w[ISSUE INCIDENT]
)
end
end
......@@ -128,6 +128,22 @@ RSpec.describe Issue do
end
end
describe '.with_issue_type' do
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:incident) { create(:incident, project: project) }
it 'gives issues with the given issue type' do
expect(described_class.with_issue_type('issue'))
.to contain_exactly(issue)
end
it 'gives issues with the given issue type' do
expect(described_class.with_issue_type(%w(issue incident)))
.to contain_exactly(issue, incident)
end
end
describe '#order_by_position_and_priority' do
let(:project) { create :project }
let(:p1) { create(:label, title: 'P1', project: project, priority: 1) }
......
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