Commit 20042768 authored by Tiger Watson's avatar Tiger Watson

Merge branch 'mo-filter-group-projects-by-ids-graphql' into 'master'

Allow namespace projects to be queried by ids with GraphQL

See merge request gitlab-org/gitlab!55383
parents 86b47641 511ab991
...@@ -17,15 +17,21 @@ module Resolvers ...@@ -17,15 +17,21 @@ module Resolvers
default_value: nil, default_value: nil,
description: 'Sort projects by this criteria.' description: 'Sort projects by this criteria.'
argument :ids, [GraphQL::ID_TYPE],
required: false,
default_value: nil,
description: 'Filter projects by IDs.'
type Types::ProjectType, null: true type Types::ProjectType, null: true
def resolve(include_subgroups:, sort:, search:) def resolve(include_subgroups:, sort:, search:, ids:)
# The namespace could have been loaded in batch by `BatchLoader`. # The namespace could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` or the `full_path` of the namespace # At this point we need the `id` or the `full_path` of the namespace
# to query for projects, so make sure it's loaded and not `nil` before continuing. # to query for projects, so make sure it's loaded and not `nil` before continuing.
return Project.none if namespace.nil? return Project.none if namespace.nil?
query = include_subgroups ? namespace.all_projects.with_route : namespace.projects.with_route query = include_subgroups ? namespace.all_projects.with_route : namespace.projects.with_route
query = ids ? query.merge(Project.where(id: parse_gids(ids))) : query # rubocop: disable CodeReuse/ActiveRecord
return query unless search.present? return query unless search.present?
...@@ -48,6 +54,10 @@ module Resolvers ...@@ -48,6 +54,10 @@ module Resolvers
object.respond_to?(:sync) ? object.sync : object object.respond_to?(:sync) ? object.sync : object
end end
end end
def parse_gids(gids)
gids&.map { |gid| GitlabSchema.parse_gid(gid, expected_type: ::Project).model_id }
end
end end
end end
......
---
title: Query group projects by ids with GraphQL
merge_request: 55383
author:
type: added
...@@ -17,8 +17,8 @@ module EE ...@@ -17,8 +17,8 @@ module EE
description: 'Returns only the projects which have vulnerabilities.' description: 'Returns only the projects which have vulnerabilities.'
end end
def resolve(include_subgroups:, search:, sort:, has_vulnerabilities: false, has_code_coverage: false) def resolve(include_subgroups:, search:, sort:, ids:, has_vulnerabilities: false, has_code_coverage: false)
projects = super(include_subgroups: include_subgroups, search: search, sort: sort) projects = super(include_subgroups: include_subgroups, search: search, sort: sort, ids: ids)
projects = projects.has_vulnerabilities if has_vulnerabilities projects = projects.has_vulnerabilities if has_vulnerabilities
projects = projects.with_code_coverage if has_code_coverage projects = projects.with_code_coverage if has_code_coverage
projects = projects.order_by_total_repository_size_excess_desc(namespace.actual_size_limit) if sort == :storage projects = projects.order_by_total_repository_size_excess_desc(namespace.actual_size_limit) if sort == :storage
......
...@@ -78,12 +78,13 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do ...@@ -78,12 +78,13 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end end
end end
def resolve_projects(has_vulnerabilities: false, sort: :similarity, has_code_coverage: false) def resolve_projects(has_vulnerabilities: false, sort: :similarity, ids: nil, has_code_coverage: false)
args = { args = {
include_subgroups: false, include_subgroups: false,
has_vulnerabilities: has_vulnerabilities, has_vulnerabilities: has_vulnerabilities,
sort: sort, sort: sort,
search: nil, search: nil,
ids: nil,
has_code_coverage: has_code_coverage has_code_coverage: has_code_coverage
} }
......
...@@ -6,6 +6,18 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do ...@@ -6,6 +6,18 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
include GraphqlHelpers include GraphqlHelpers
let(:current_user) { create(:user) } let(:current_user) { create(:user) }
let(:include_subgroups) { true }
let(:sort) { nil }
let(:search) { nil }
let(:ids) { nil }
let(:args) do
{
include_subgroups: include_subgroups,
sort: sort,
search: search,
ids: ids
}
end
context "with a group" do context "with a group" do
let(:group) { create(:group) } let(:group) { create(:group) }
...@@ -27,7 +39,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do ...@@ -27,7 +39,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end end
it 'finds all projects including the subgroups' do it 'finds all projects including the subgroups' do
expect(resolve_projects(include_subgroups: true, sort: nil, search: nil)).to contain_exactly(project1, project2, nested_project) expect(resolve_projects(args)).to contain_exactly(project1, project2, nested_project)
end end
context 'with an user namespace' do context 'with an user namespace' do
...@@ -38,7 +50,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do ...@@ -38,7 +50,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end end
it 'finds all projects including the subgroups' do it 'finds all projects including the subgroups' do
expect(resolve_projects(include_subgroups: true, sort: nil, search: nil)).to contain_exactly(project1, project2) expect(resolve_projects(args)).to contain_exactly(project1, project2)
end end
end end
end end
...@@ -48,6 +60,9 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do ...@@ -48,6 +60,9 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
let(:project_2) { create(:project, name: 'Test Project', path: 'test-project', namespace: namespace) } let(:project_2) { create(:project, name: 'Test Project', path: 'test-project', namespace: namespace) }
let(:project_3) { create(:project, name: 'Test', path: 'test', namespace: namespace) } let(:project_3) { create(:project, name: 'Test', path: 'test', namespace: namespace) }
let(:sort) { :similarity }
let(:search) { 'test' }
before do before do
project_1.add_developer(current_user) project_1.add_developer(current_user)
project_2.add_developer(current_user) project_2.add_developer(current_user)
...@@ -55,7 +70,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do ...@@ -55,7 +70,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end end
it 'returns projects ordered by similarity to the search input' do it 'returns projects ordered by similarity to the search input' do
projects = resolve_projects(include_subgroups: true, sort: :similarity, search: 'test') projects = resolve_projects(args)
project_names = projects.map { |proj| proj['name'] } project_names = projects.map { |proj| proj['name'] }
expect(project_names.first).to eq('Test') expect(project_names.first).to eq('Test')
...@@ -63,15 +78,17 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do ...@@ -63,15 +78,17 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end end
it 'filters out result that do not match the search input' do it 'filters out result that do not match the search input' do
projects = resolve_projects(include_subgroups: true, sort: :similarity, search: 'test') projects = resolve_projects(args)
project_names = projects.map { |proj| proj['name'] } project_names = projects.map { |proj| proj['name'] }
expect(project_names).not_to include('Project') expect(project_names).not_to include('Project')
end end
context 'when `search` parameter is not given' do context 'when `search` parameter is not given' do
let(:search) { nil }
it 'returns projects not ordered by similarity' do it 'returns projects not ordered by similarity' do
projects = resolve_projects(include_subgroups: true, sort: :similarity, search: nil) projects = resolve_projects(args)
project_names = projects.map { |proj| proj['name'] } project_names = projects.map { |proj| proj['name'] }
expect(project_names.first).not_to eq('Test') expect(project_names.first).not_to eq('Test')
...@@ -79,14 +96,40 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do ...@@ -79,14 +96,40 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end end
context 'when only search term is given' do context 'when only search term is given' do
let(:sort) { nil }
let(:search) { 'test' }
it 'filters out result that do not match the search input, but does not sort them' do it 'filters out result that do not match the search input, but does not sort them' do
projects = resolve_projects(include_subgroups: true, sort: :nil, search: 'test') projects = resolve_projects(args)
project_names = projects.map { |proj| proj['name'] } project_names = projects.map { |proj| proj['name'] }
expect(project_names).to contain_exactly('Test', 'Test Project') expect(project_names).to contain_exactly('Test', 'Test Project')
end end
end end
end end
context 'ids filtering' do
subject(:projects) { resolve_projects(args) }
let(:include_subgroups) { false }
let(:project_3) { create(:project, name: 'Project', path: 'project', namespace: namespace) }
context 'when ids is provided' do
let(:ids) { [project_3.to_global_id.to_s] }
it 'returns matching project' do
expect(projects).to contain_exactly(project_3)
end
end
context 'when ids is nil' do
let(:ids) { nil }
it 'returns all projects' do
expect(projects).to contain_exactly(project1, project2, project_3)
end
end
end
end end
context "when passing a non existent, batch loaded namespace" do context "when passing a non existent, batch loaded namespace" do
...@@ -108,7 +151,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do ...@@ -108,7 +151,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24 expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24
end end
def resolve_projects(args = { include_subgroups: false, sort: nil, search: nil }, context = { current_user: current_user }) def resolve_projects(args = { include_subgroups: false, sort: nil, search: nil, ids: nil }, context = { current_user: current_user })
resolve(described_class, obj: namespace, args: args, ctx: context) resolve(described_class, obj: namespace, args: args, ctx: context)
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