Commit 1f4e5fe7 authored by Robert Speicher's avatar Robert Speicher

Merge branch...

Merge branch '56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql' into 'master'

Implement new arguments `state`, `closed_before` and `closed_after` for `IssuesResolver` in GraphQL

Closes #56492

See merge request gitlab-org/gitlab-ce!24910
parents 0b846d7c 87dfe5a2
...@@ -94,6 +94,7 @@ class IssuableFinder ...@@ -94,6 +94,7 @@ class IssuableFinder
items = by_scope(items) items = by_scope(items)
items = by_created_at(items) items = by_created_at(items)
items = by_updated_at(items) items = by_updated_at(items)
items = by_closed_at(items)
items = by_state(items) items = by_state(items)
items = by_group(items) items = by_group(items)
items = by_assignee(items) items = by_assignee(items)
...@@ -353,6 +354,13 @@ class IssuableFinder ...@@ -353,6 +354,13 @@ class IssuableFinder
items items
end end
def by_closed_at(items)
items = items.closed_after(params[:closed_after]) if params[:closed_after].present?
items = items.closed_before(params[:closed_before]) if params[:closed_before].present?
items
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def by_state(items) def by_state(items)
case params[:state].to_s case params[:state].to_s
......
...@@ -9,7 +9,30 @@ module Resolvers ...@@ -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,
......
# 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
# 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 ...@@ -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,
......
# 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 ...@@ -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
......
# frozen_string_literal: true
module ClosedAtFilterable
extend ActiveSupport::Concern
included do
scope :closed_before, ->(date) { where(scoped_table[:closed_at].lteq(date)) }
scope :closed_after, ->(date) { where(scoped_table[:closed_at].gteq(date)) }
def self.scoped_table
arel_table.alias(table_name)
end
end
end
...@@ -23,6 +23,7 @@ module Issuable ...@@ -23,6 +23,7 @@ module Issuable
include Sortable include Sortable
include CreatedAtFilterable include CreatedAtFilterable
include UpdatedAtFilterable include UpdatedAtFilterable
include ClosedAtFilterable
# This object is used to gather issuable meta data for displaying # This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests # upvotes, downvotes, notes and closing merge requests count for issues and merge requests
......
---
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: ...@@ -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.
......
...@@ -416,6 +416,36 @@ describe IssuesFinder do ...@@ -416,6 +416,36 @@ describe IssuesFinder do
end end
end end
context 'filtering by closed_at' do
let!(:closed_issue1) { create(:issue, project: project1, state: :closed, closed_at: 1.week.ago) }
let!(:closed_issue2) { create(:issue, project: project2, state: :closed, closed_at: 1.week.from_now) }
let!(:closed_issue3) { create(:issue, project: project2, state: :closed, closed_at: 2.weeks.from_now) }
context 'through closed_after' do
let(:params) { { state: :closed, closed_after: closed_issue3.closed_at } }
it 'returns issues closed on or after the given date' do
expect(issues).to contain_exactly(closed_issue3)
end
end
context 'through closed_before' do
let(:params) { { state: :closed, closed_before: closed_issue1.closed_at } }
it 'returns issues closed on or before the given date' do
expect(issues).to contain_exactly(closed_issue1)
end
end
context 'through closed_after and closed_before' do
let(:params) { { state: :closed, closed_after: closed_issue2.closed_at, closed_before: closed_issue3.closed_at } }
it 'returns issues closed between the given dates' do
expect(issues).to contain_exactly(closed_issue2, closed_issue3)
end
end
end
context 'filtering by reaction name' do context 'filtering by reaction name' do
context 'user searches by no reaction' do context 'user searches by no reaction' do
let(:params) { { my_reaction_emoji: 'None' } } let(:params) { { my_reaction_emoji: 'None' } }
......
...@@ -5,16 +5,63 @@ describe Resolvers::IssuesResolver do ...@@ -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 ...@@ -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 ...@@ -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
......
# 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
# 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
# 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
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
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