Add GraphQL filters for issuables (state, labels, time fields)
Signed-off-by: Rémy Coutable <remy@rymai.me>
This commit is contained in:
parent
f0a2c4116c
commit
87dfe5a27a
|
@ -9,7 +9,30 @@ module Resolvers
|
||||||
argument :iids, [GraphQL::ID_TYPE],
|
argument :iids, [GraphQL::ID_TYPE],
|
||||||
required: false,
|
required: false,
|
||||||
description: 'The list of IIDs of issues, e.g., [1, 2]'
|
description: 'The list of IIDs of issues, e.g., [1, 2]'
|
||||||
|
argument :state, Types::IssuableStateEnum,
|
||||||
|
required: false,
|
||||||
|
description: "Current state of Issue"
|
||||||
|
argument :label_name, GraphQL::STRING_TYPE.to_list_type,
|
||||||
|
required: false,
|
||||||
|
description: "Labels applied to the Issue"
|
||||||
|
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,
|
argument :search, GraphQL::STRING_TYPE,
|
||||||
required: false
|
required: false
|
||||||
argument :sort, Types::Sort,
|
argument :sort, Types::Sort,
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Types
|
||||||
|
class IssuableStateEnum < BaseEnum
|
||||||
|
graphql_name 'IssuableState'
|
||||||
|
description 'State of a GitLab issue or merge request'
|
||||||
|
|
||||||
|
value 'opened'
|
||||||
|
value 'closed'
|
||||||
|
value 'locked'
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Types
|
||||||
|
class IssueStateEnum < IssuableStateEnum
|
||||||
|
graphql_name 'IssueState'
|
||||||
|
description 'State of a GitLab issue'
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,7 +11,7 @@ module Types
|
||||||
field :iid, GraphQL::ID_TYPE, null: false
|
field :iid, GraphQL::ID_TYPE, null: false
|
||||||
field :title, GraphQL::STRING_TYPE, null: false
|
field :title, GraphQL::STRING_TYPE, null: false
|
||||||
field :description, GraphQL::STRING_TYPE, null: true
|
field :description, GraphQL::STRING_TYPE, null: true
|
||||||
field :state, GraphQL::STRING_TYPE, null: false
|
field :state, IssueStateEnum, null: false
|
||||||
|
|
||||||
field :author, Types::UserType,
|
field :author, Types::UserType,
|
||||||
null: false,
|
null: false,
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Types
|
||||||
|
class MergeRequestStateEnum < IssuableStateEnum
|
||||||
|
graphql_name 'MergeRequestState'
|
||||||
|
description 'State of a GitLab merge request'
|
||||||
|
|
||||||
|
value 'merged'
|
||||||
|
end
|
||||||
|
end
|
|
@ -12,7 +12,7 @@ module Types
|
||||||
field :iid, GraphQL::ID_TYPE, null: false
|
field :iid, GraphQL::ID_TYPE, null: false
|
||||||
field :title, GraphQL::STRING_TYPE, null: false
|
field :title, GraphQL::STRING_TYPE, null: false
|
||||||
field :description, GraphQL::STRING_TYPE, null: true
|
field :description, GraphQL::STRING_TYPE, null: true
|
||||||
field :state, GraphQL::STRING_TYPE, null: true
|
field :state, MergeRequestStateEnum, null: false
|
||||||
field :created_at, Types::TimeType, null: false
|
field :created_at, Types::TimeType, null: false
|
||||||
field :updated_at, Types::TimeType, null: false
|
field :updated_at, Types::TimeType, null: false
|
||||||
field :source_project, Types::ProjectType, null: true
|
field :source_project, Types::ProjectType, null: true
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: "Implement new arguments `state`, `closed_before` and `closed_after` for `IssuesResolver` in GraphQL"
|
||||||
|
merge_request: 24910
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -134,7 +134,7 @@ Endpoints are available for:
|
||||||
## Road to GraphQL
|
## Road to GraphQL
|
||||||
|
|
||||||
Going forward, we will start on moving to
|
Going forward, we will start on moving to
|
||||||
[GraphQL](http://graphql.org/learn/best-practices/) and deprecate the use of
|
[GraphQL](graphql/index.md) and deprecate the use of
|
||||||
controller-specific endpoints. GraphQL has a number of benefits:
|
controller-specific endpoints. GraphQL has a number of benefits:
|
||||||
|
|
||||||
1. We avoid having to maintain two different APIs.
|
1. We avoid having to maintain two different APIs.
|
||||||
|
|
|
@ -5,16 +5,63 @@ describe Resolvers::IssuesResolver do
|
||||||
|
|
||||||
let(:current_user) { create(:user) }
|
let(:current_user) { create(:user) }
|
||||||
set(:project) { create(:project) }
|
set(:project) { create(:project) }
|
||||||
set(:issue) { create(:issue, project: project) }
|
set(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) }
|
||||||
set(:issue2) { create(:issue, project: project, title: 'foo') }
|
set(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) }
|
||||||
|
set(:label1) { create(:label, project: project) }
|
||||||
|
set(:label2) { create(:label, project: project) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.add_developer(current_user)
|
project.add_developer(current_user)
|
||||||
|
create(:label_link, label: label1, target: issue1)
|
||||||
|
create(:label_link, label: label1, target: issue2)
|
||||||
|
create(:label_link, label: label2, target: issue2)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#resolve' do
|
describe '#resolve' do
|
||||||
it 'finds all issues' do
|
it 'finds all issues' do
|
||||||
expect(resolve_issues).to contain_exactly(issue, issue2)
|
expect(resolve_issues).to contain_exactly(issue1, issue2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters by state' do
|
||||||
|
expect(resolve_issues(state: 'opened')).to contain_exactly(issue1)
|
||||||
|
expect(resolve_issues(state: 'closed')).to contain_exactly(issue2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters by labels' do
|
||||||
|
expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2)
|
||||||
|
expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'filters by created_at' do
|
||||||
|
it 'filters by created_before' do
|
||||||
|
expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters by created_after' do
|
||||||
|
expect(resolve_issues(created_after: 2.hours.ago)).to contain_exactly(issue2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'filters by updated_at' do
|
||||||
|
it 'filters by updated_before' do
|
||||||
|
expect(resolve_issues(updated_before: 2.hours.ago)).to contain_exactly(issue1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters by updated_after' do
|
||||||
|
expect(resolve_issues(updated_after: 2.hours.ago)).to contain_exactly(issue2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'filters by closed_at' do
|
||||||
|
let!(:issue3) { create(:issue, project: project, state: :closed, closed_at: 3.hours.ago) }
|
||||||
|
|
||||||
|
it 'filters by closed_before' do
|
||||||
|
expect(resolve_issues(closed_before: 2.hours.ago)).to contain_exactly(issue3)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters by closed_after' do
|
||||||
|
expect(resolve_issues(closed_after: 2.hours.ago)).to contain_exactly(issue2)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'searches issues' do
|
it 'searches issues' do
|
||||||
|
@ -22,7 +69,7 @@ describe Resolvers::IssuesResolver do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sort issues' do
|
it 'sort issues' do
|
||||||
expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue]
|
expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1]
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns issues user can see' do
|
it 'returns issues user can see' do
|
||||||
|
@ -30,31 +77,31 @@ describe Resolvers::IssuesResolver do
|
||||||
|
|
||||||
create(:issue, confidential: true)
|
create(:issue, confidential: true)
|
||||||
|
|
||||||
expect(resolve_issues).to contain_exactly(issue, issue2)
|
expect(resolve_issues).to contain_exactly(issue1, issue2)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'finds a specific issue with iid' do
|
it 'finds a specific issue with iid' do
|
||||||
expect(resolve_issues(iid: issue.iid)).to contain_exactly(issue)
|
expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'finds a specific issue with iids' do
|
it 'finds a specific issue with iids' do
|
||||||
expect(resolve_issues(iids: issue.iid)).to contain_exactly(issue)
|
expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'finds multiple issues with iids' do
|
it 'finds multiple issues with iids' do
|
||||||
expect(resolve_issues(iids: [issue.iid, issue2.iid]))
|
expect(resolve_issues(iids: [issue1.iid, issue2.iid]))
|
||||||
.to contain_exactly(issue, issue2)
|
.to contain_exactly(issue1, issue2)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'finds only the issues within the project we are looking at' do
|
it 'finds only the issues within the project we are looking at' do
|
||||||
another_project = create(:project)
|
another_project = create(:project)
|
||||||
iids = [issue, issue2].map(&:iid)
|
iids = [issue1, issue2].map(&:iid)
|
||||||
|
|
||||||
iids.each do |iid|
|
iids.each do |iid|
|
||||||
create(:issue, project: another_project, iid: iid)
|
create(:issue, project: another_project, iid: iid)
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(resolve_issues(iids: iids)).to contain_exactly(issue, issue2)
|
expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe GitlabSchema.types['IssuableState'] do
|
||||||
|
it { expect(described_class.graphql_name).to eq('IssuableState') }
|
||||||
|
|
||||||
|
it_behaves_like 'issuable state'
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe GitlabSchema.types['IssueState'] do
|
||||||
|
it { expect(described_class.graphql_name).to eq('IssueState') }
|
||||||
|
|
||||||
|
it_behaves_like 'issuable state'
|
||||||
|
end
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe GitlabSchema.types['MergeRequestState'] do
|
||||||
|
it { expect(described_class.graphql_name).to eq('MergeRequestState') }
|
||||||
|
|
||||||
|
it_behaves_like 'issuable state'
|
||||||
|
|
||||||
|
it 'exposes all the existing merge request states' do
|
||||||
|
expect(described_class.values.keys).to include('merged')
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
RSpec.shared_examples 'issuable state' do
|
||||||
|
it 'exposes all the existing issuable states' do
|
||||||
|
expect(described_class.values.keys).to include(*%w[opened closed locked])
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue