Commit 16933d16 authored by Adam Hegyi's avatar Adam Hegyi

Add filter arguments to MR GraphQL API

Allow filtering MRs by the following arguments:

- Author
- Assignee
- Milestone
parent c909c0ec
...@@ -29,11 +29,14 @@ module Resolvers ...@@ -29,11 +29,14 @@ module Resolvers
as: :label_name, as: :label_name,
description: 'Array of label names. All resolved merge requests will have all of these labels.' description: 'Array of label names. All resolved merge requests will have all of these labels.'
argument :merged_after, Types::TimeType, argument :merged_after, Types::TimeType,
required: false, required: false,
description: 'Merge requests merged after this date' description: 'Merge requests merged after this date'
argument :merged_before, Types::TimeType, argument :merged_before, Types::TimeType,
required: false, required: false,
description: 'Merge requests merged before this date' description: 'Merge requests merged before this date'
argument :milestone_title, GraphQL::STRING_TYPE,
required: false,
description: 'Title of the milestone'
def self.single def self.single
::Resolvers::MergeRequestResolver ::Resolvers::MergeRequestResolver
......
# frozen_string_literal: true
module Resolvers
class ProjectMergeRequestsResolver < MergeRequestsResolver
argument :assignee_username, GraphQL::STRING_TYPE,
required: false,
description: 'Username of the assignee'
argument :author_username, GraphQL::STRING_TYPE,
required: false,
description: 'Username of the author'
end
end
...@@ -134,7 +134,7 @@ module Types ...@@ -134,7 +134,7 @@ module Types
null: true, null: true,
description: 'Merge requests of the project', description: 'Merge requests of the project',
extras: [:lookahead], extras: [:lookahead],
resolver: Resolvers::MergeRequestsResolver resolver: Resolvers::ProjectMergeRequestsResolver
field :merge_request, field :merge_request,
Types::MergeRequestType, Types::MergeRequestType,
......
---
title: Filter Merge Requests by author, assignee and milestone in GraphQL
merge_request: 40265
author:
type: added
...@@ -11419,6 +11419,16 @@ type Project { ...@@ -11419,6 +11419,16 @@ type Project {
""" """
after: String after: String
"""
Username of the assignee
"""
assigneeUsername: String
"""
Username of the author
"""
authorUsername: String
""" """
Returns the elements in the list that come before the specified cursor. Returns the elements in the list that come before the specified cursor.
""" """
...@@ -11454,6 +11464,11 @@ type Project { ...@@ -11454,6 +11464,11 @@ type Project {
""" """
mergedBefore: Time mergedBefore: Time
"""
Title of the milestone
"""
milestoneTitle: String
""" """
Array of source branch names. All resolved merge requests will have one of these branches as their source. Array of source branch names. All resolved merge requests will have one of these branches as their source.
""" """
...@@ -16511,6 +16526,11 @@ type User { ...@@ -16511,6 +16526,11 @@ type User {
""" """
mergedBefore: Time mergedBefore: Time
"""
Title of the milestone
"""
milestoneTitle: String
""" """
The global ID of the project the authored merge requests should be in. Incompatible with projectPath. The global ID of the project the authored merge requests should be in. Incompatible with projectPath.
""" """
...@@ -16581,6 +16601,11 @@ type User { ...@@ -16581,6 +16601,11 @@ type User {
""" """
mergedBefore: Time mergedBefore: Time
"""
Title of the milestone
"""
milestoneTitle: String
""" """
The global ID of the project the authored merge requests should be in. Incompatible with projectPath. The global ID of the project the authored merge requests should be in. Incompatible with projectPath.
""" """
......
...@@ -33824,6 +33824,36 @@ ...@@ -33824,6 +33824,36 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "milestoneTitle",
"description": "Title of the milestone",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "assigneeUsername",
"description": "Username of the assignee",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "authorUsername",
"description": "Username of the author",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{ {
"name": "after", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
...@@ -48486,6 +48516,16 @@ ...@@ -48486,6 +48516,16 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "milestoneTitle",
"description": "Title of the milestone",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{ {
"name": "projectPath", "name": "projectPath",
"description": "The full-path of the project the authored merge requests should be in. Incompatible with projectId.", "description": "The full-path of the project the authored merge requests should be in. Incompatible with projectId.",
...@@ -48661,6 +48701,16 @@ ...@@ -48661,6 +48701,16 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "milestoneTitle",
"description": "Title of the milestone",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{ {
"name": "projectPath", "name": "projectPath",
"description": "The full-path of the project the authored merge requests should be in. Incompatible with projectId.", "description": "The full-path of the project the authored merge requests should be in. Incompatible with projectId.",
...@@ -6,7 +6,9 @@ RSpec.describe Resolvers::MergeRequestsResolver do ...@@ -6,7 +6,9 @@ RSpec.describe Resolvers::MergeRequestsResolver do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:common_attrs) { { author: current_user, source_project: project, target_project: project } } let_it_be(:common_attrs) { { author: current_user, source_project: project, target_project: project } }
let_it_be(:merge_request_1) { create(:merge_request, :simple, **common_attrs) } let_it_be(:merge_request_1) { create(:merge_request, :simple, **common_attrs) }
let_it_be(:merge_request_2) { create(:merge_request, :rebased, **common_attrs) } let_it_be(:merge_request_2) { create(:merge_request, :rebased, **common_attrs) }
...@@ -14,8 +16,10 @@ RSpec.describe Resolvers::MergeRequestsResolver do ...@@ -14,8 +16,10 @@ RSpec.describe Resolvers::MergeRequestsResolver do
let_it_be(:merge_request_4) { create(:merge_request, :unique_branches, :locked, **common_attrs) } let_it_be(:merge_request_4) { create(:merge_request, :unique_branches, :locked, **common_attrs) }
let_it_be(:merge_request_5) { create(:merge_request, :simple, :locked, **common_attrs) } let_it_be(:merge_request_5) { create(:merge_request, :simple, :locked, **common_attrs) }
let_it_be(:merge_request_6) { create(:labeled_merge_request, :unique_branches, labels: create_list(:label, 2), **common_attrs) } let_it_be(:merge_request_6) { create(:labeled_merge_request, :unique_branches, labels: create_list(:label, 2), **common_attrs) }
let_it_be(:merge_request_with_milestone) { create(:merge_request, :unique_branches, **common_attrs, milestone: milestone) }
let_it_be(:other_project) { create(:project, :repository) } let_it_be(:other_project) { create(:project, :repository) }
let_it_be(:other_merge_request) { create(:merge_request, source_project: other_project, target_project: other_project) } let_it_be(:other_merge_request) { create(:merge_request, source_project: other_project, target_project: other_project) }
let(:iid_1) { merge_request_1.iid } let(:iid_1) { merge_request_1.iid }
let(:iid_2) { merge_request_2.iid } let(:iid_2) { merge_request_2.iid }
let(:other_iid) { other_merge_request.iid } let(:other_iid) { other_merge_request.iid }
...@@ -32,7 +36,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do ...@@ -32,7 +36,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do
it 'returns all merge requests' do it 'returns all merge requests' do
result = resolve_mr(project, {}) result = resolve_mr(project, {})
expect(result).to contain_exactly(merge_request_1, merge_request_2, merge_request_3, merge_request_4, merge_request_5, merge_request_6) expect(result).to contain_exactly(merge_request_1, merge_request_2, merge_request_3, merge_request_4, merge_request_5, merge_request_6, merge_request_with_milestone)
end end
it 'returns only merge requests that the current user can see' do it 'returns only merge requests that the current user can see' do
...@@ -179,6 +183,20 @@ RSpec.describe Resolvers::MergeRequestsResolver do ...@@ -179,6 +183,20 @@ RSpec.describe Resolvers::MergeRequestsResolver do
end end
end end
context 'by milestone' do
it 'filters merge requests by milestone title' do
result = resolve_mr(project, milestone_title: milestone.title)
expect(result).to eq([merge_request_with_milestone])
end
it 'does not find anything' do
result = resolve_mr(project, milestone_title: 'unknown-milestone')
expect(result).to be_empty
end
end
describe 'combinations' do describe 'combinations' do
it 'requires all filters' do it 'requires all filters' do
create(:merge_request, :closed, source_project: project, target_project: project, source_branch: merge_request_4.source_branch) create(:merge_request, :closed, source_project: project, target_project: project, source_branch: merge_request_4.source_branch)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::ProjectMergeRequestsResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:current_user) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:merge_request_with_author_and_assignee) do
create(:merge_request,
:unique_branches,
source_project: project,
target_project: project,
author: other_user,
assignee: other_user)
end
before do
project.add_developer(current_user)
end
context 'by assignee' do
it 'filters merge requests by assignee username' do
result = resolve_mr(project, assignee_username: other_user.username)
expect(result).to eq([merge_request_with_author_and_assignee])
end
it 'does not find anything' do
result = resolve_mr(project, assignee_username: 'unknown-user')
expect(result).to be_empty
end
end
context 'by author' do
it 'filters merge requests by author username' do
result = resolve_mr(project, author_username: other_user.username)
expect(result).to eq([merge_request_with_author_and_assignee])
end
it 'does not find anything' do
result = resolve_mr(project, author_username: 'unknown-user')
expect(result).to be_empty
end
end
def resolve_mr(project, args, resolver: described_class, user: current_user)
resolve(resolver, obj: project, args: args, ctx: { current_user: user })
end
end
...@@ -59,7 +59,7 @@ RSpec.describe GitlabSchema.types['Project'] do ...@@ -59,7 +59,7 @@ RSpec.describe GitlabSchema.types['Project'] do
subject { described_class.fields['mergeRequests'] } subject { described_class.fields['mergeRequests'] }
it { is_expected.to have_graphql_type(Types::MergeRequestType.connection_type) } it { is_expected.to have_graphql_type(Types::MergeRequestType.connection_type) }
it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver) } it { is_expected.to have_graphql_resolver(Resolvers::ProjectMergeRequestsResolver) }
it do it do
is_expected.to have_graphql_arguments(:iids, is_expected.to have_graphql_arguments(:iids,
...@@ -72,7 +72,10 @@ RSpec.describe GitlabSchema.types['Project'] do ...@@ -72,7 +72,10 @@ RSpec.describe GitlabSchema.types['Project'] do
:first, :first,
:last, :last,
:merged_after, :merged_after,
:merged_before :merged_before,
:author_username,
:assignee_username,
:milestone_title
) )
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