Commit 2dbb2234 authored by Sean McGivern's avatar Sean McGivern

Merge branch '285467-package-registry-graphql-api-3' into 'master'

Package: group and project graphql types - add search

See merge request gitlab-org/gitlab!61001
parents 0df2955c a30c143b
# frozen_string_literal: true
# rubocop: disable Graphql/ResolverType
module Resolvers
class GroupPackagesResolver < BaseResolver
type Types::Packages::PackageType.connection_type, null: true
class GroupPackagesResolver < PackagesBaseResolver
# The GraphQL type is defined in the extended class
argument :sort, Types::Packages::PackageGroupSortEnum,
description: 'Sort packages by this criteria.',
required: false,
default_value: :created_desc
SORT_TO_PARAMS_MAP = {
created_desc: { order_by: 'created', sort: 'desc' },
created_asc: { order_by: 'created', sort: 'asc' },
name_desc: { order_by: 'name', sort: 'desc' },
name_asc: { order_by: 'name', sort: 'asc' },
version_desc: { order_by: 'version', sort: 'desc' },
version_asc: { order_by: 'version', sort: 'asc' },
type_desc: { order_by: 'type', sort: 'desc' },
type_asc: { order_by: 'type', sort: 'asc' },
GROUP_SORT_TO_PARAMS_MAP = SORT_TO_PARAMS_MAP.merge({
project_path_desc: { order_by: 'project_path', sort: 'desc' },
project_path_asc: { order_by: 'project_path', sort: 'asc' }
}.freeze
}).freeze
def ready?(**args)
context[self.class] ||= { executions: 0 }
......@@ -30,16 +23,11 @@ module Resolvers
super
end
def resolve(sort:)
def resolve(sort:, **filters)
return unless packages_available?
::Packages::GroupPackagesFinder.new(current_user, object, SORT_TO_PARAMS_MAP[sort]).execute
end
private
def packages_available?
::Gitlab.config.packages.enabled
::Packages::GroupPackagesFinder.new(current_user, object, filters.merge(GROUP_SORT_TO_PARAMS_MAP.fetch(sort))).execute
end
end
end
# rubocop: enable Graphql/ResolverType
# frozen_string_literal: true
module Resolvers
class PackagesBaseResolver < BaseResolver
type Types::Packages::PackageType.connection_type, null: true
argument :sort, Types::Packages::PackageSortEnum,
description: 'Sort packages by this criteria.',
required: false,
default_value: :created_desc
argument :package_name, GraphQL::STRING_TYPE,
description: 'Search a package by name.',
required: false,
default_value: nil
argument :package_type, Types::Packages::PackageTypeEnum,
description: 'Filter a package by type.',
required: false,
default_value: nil
argument :status, Types::Packages::PackageStatusEnum,
description: 'Filter a package by status.',
required: false,
default_value: nil
argument :include_versionless, GraphQL::BOOLEAN_TYPE,
description: 'Include versionless packages.',
required: false,
default_value: false
SORT_TO_PARAMS_MAP = {
created_desc: { order_by: 'created', sort: 'desc' },
created_asc: { order_by: 'created', sort: 'asc' },
name_desc: { order_by: 'name', sort: 'desc' },
name_asc: { order_by: 'name', sort: 'asc' },
version_desc: { order_by: 'version', sort: 'desc' },
version_asc: { order_by: 'version', sort: 'asc' },
type_desc: { order_by: 'type', sort: 'desc' },
type_asc: { order_by: 'type', sort: 'asc' }
}.freeze
def resolve
raise NotImplementedError
end
private
def packages_available?
::Gitlab.config.packages.enabled
end
end
end
# frozen_string_literal: true
# rubocop: disable Graphql/ResolverType
module Resolvers
class ProjectPackagesResolver < BaseResolver
type Types::Packages::PackageType.connection_type, null: true
class ProjectPackagesResolver < PackagesBaseResolver
# The GraphQL type is defined in the extended class
argument :sort, Types::Packages::PackageSortEnum,
description: 'Sort packages by this criteria.',
required: false,
default_value: :created_desc
SORT_TO_PARAMS_MAP = {
created_desc: { order_by: 'created', sort: 'desc' },
created_asc: { order_by: 'created', sort: 'asc' },
name_desc: { order_by: 'name', sort: 'desc' },
name_asc: { order_by: 'name', sort: 'asc' },
version_desc: { order_by: 'version', sort: 'desc' },
version_asc: { order_by: 'version', sort: 'asc' },
type_desc: { order_by: 'type', sort: 'desc' },
type_asc: { order_by: 'type', sort: 'asc' }
}.freeze
def resolve(sort:)
def resolve(sort:, **filters)
return unless packages_available?
::Packages::PackagesFinder.new(object, SORT_TO_PARAMS_MAP.fetch(sort)).execute
end
private
def packages_available?
::Gitlab.config.packages.enabled
::Packages::PackagesFinder.new(object, filters.merge(SORT_TO_PARAMS_MAP.fetch(sort))).execute
end
end
end
# rubocop: enable Graphql/ResolverType
---
title: 'Package: group and project graphql types - add search'
merge_request: 61001
author:
type: added
......@@ -9107,7 +9107,11 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="grouppackagesincludeversionless"></a>`includeVersionless` | [`Boolean`](#boolean) | Include versionless packages. |
| <a id="grouppackagespackagename"></a>`packageName` | [`String`](#string) | Search a package by name. |
| <a id="grouppackagespackagetype"></a>`packageType` | [`PackageTypeEnum`](#packagetypeenum) | Filter a package by type. |
| <a id="grouppackagessort"></a>`sort` | [`PackageGroupSort`](#packagegroupsort) | Sort packages by this criteria. |
| <a id="grouppackagesstatus"></a>`status` | [`PackageStatus`](#packagestatus) | Filter a package by status. |
##### `Group.projects`
......@@ -11362,7 +11366,11 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectpackagesincludeversionless"></a>`includeVersionless` | [`Boolean`](#boolean) | Include versionless packages. |
| <a id="projectpackagespackagename"></a>`packageName` | [`String`](#string) | Search a package by name. |
| <a id="projectpackagespackagetype"></a>`packageType` | [`PackageTypeEnum`](#packagetypeenum) | Filter a package by type. |
| <a id="projectpackagessort"></a>`sort` | [`PackageSort`](#packagesort) | Sort packages by this criteria. |
| <a id="projectpackagesstatus"></a>`status` | [`PackageStatus`](#packagestatus) | Filter a package by status. |
##### `Project.pipeline`
......
......@@ -16,38 +16,6 @@ RSpec.describe Resolvers::GroupPackagesResolver do
describe '#resolve' do
subject { resolve(described_class, ctx: { current_user: user }, obj: group, args: args).to_a }
context 'without sort' do
let_it_be(:package) { create(:package, project: project) }
it { is_expected.to contain_exactly(package) }
end
context 'with a sort argument' do
let_it_be(:project2) { create(:project, :public, group: group) }
let_it_be(:sort_repository) do
create(:conan_package, name: 'bar', project: project, created_at: 1.day.ago, version: "1.0.0")
end
let_it_be(:sort_repository2) do
create(:maven_package, name: 'foo', project: project2, created_at: 1.hour.ago, version: "2.0.0")
end
[:created_desc, :name_desc, :version_desc, :type_asc, :project_path_desc].each do |order|
context "#{order}" do
let(:args) { { sort: order } }
it { is_expected.to eq([sort_repository2, sort_repository]) }
end
end
[:created_asc, :name_asc, :version_asc, :type_desc, :project_path_asc].each do |order|
context "#{order}" do
let(:args) { { sort: order } }
it { is_expected.to eq([sort_repository, sort_repository2]) }
end
end
end
it_behaves_like 'group and projects packages resolver'
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::PackagesBaseResolver do
include GraphqlHelpers
describe '#resolve' do
subject { resolve(described_class) }
it 'throws an error' do
expect { subject }.to raise_error(NotImplementedError)
end
end
end
......@@ -6,7 +6,7 @@ RSpec.describe Resolvers::ProjectPackagesResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
let_it_be_with_reload(:project) { create(:project, :public) }
let(:args) do
{ sort: :created_desc }
......@@ -15,36 +15,6 @@ RSpec.describe Resolvers::ProjectPackagesResolver do
describe '#resolve' do
subject { resolve(described_class, ctx: { current_user: user }, obj: project, args: args).to_a }
context 'without sort' do
let_it_be(:package) { create(:package, project: project) }
it { is_expected.to contain_exactly(package) }
end
context 'with a sort argument' do
let_it_be(:sort_repository) do
create(:conan_package, name: 'bar', project: project, created_at: 1.day.ago, version: "1.0.0")
end
let_it_be(:sort_repository2) do
create(:maven_package, name: 'foo', project: project, created_at: 1.hour.ago, version: "2.0.0")
end
[:created_desc, :name_desc, :version_desc, :type_asc].each do |order|
context "#{order}" do
let(:args) { { sort: order } }
it { is_expected.to eq([sort_repository2, sort_repository]) }
end
end
[:created_asc, :name_asc, :version_asc, :type_desc].each do |order|
context "#{order}" do
let(:args) { { sort: order } }
it { is_expected.to eq([sort_repository, sort_repository2]) }
end
end
end
it_behaves_like 'group and projects packages resolver'
end
end
......@@ -7,82 +7,19 @@ RSpec.describe 'getting a package list for a group' do
let_it_be(:resource) { create(:group, :private) }
let_it_be(:group_two) { create(:group, :private) }
let_it_be(:project) { create(:project, :repository, group: resource) }
let_it_be(:another_project) { create(:project, :repository, group: resource) }
let_it_be(:group_two_project) { create(:project, :repository, group: group_two) }
let_it_be(:project1) { create(:project, :repository, group: resource) }
let_it_be(:project2) { create(:project, :repository, group: resource) }
let_it_be(:current_user) { create(:user) }
let_it_be(:npm_package) { create(:npm_package, project: group_two_project) }
let_it_be(:maven_package) { create(:maven_package, project: project, name: 'tab', version: '4.0.0', created_at: 5.days.ago) }
let_it_be(:package) { create(:npm_package, project: project, name: 'uab', version: '5.0.0', created_at: 4.days.ago) }
let_it_be(:composer_package) { create(:composer_package, project: another_project, name: 'vab', version: '6.0.0', created_at: 3.days.ago) }
let_it_be(:debian_package) { create(:debian_package, project: another_project, name: 'zab', version: '7.0.0', created_at: 2.days.ago) }
let_it_be(:composer_metadatum) do
create(:composer_metadatum, package: composer_package,
target_sha: 'afdeh',
composer_json: { name: 'x', type: 'y', license: 'z', version: 1 })
end
let(:package_names) { graphql_data_at(:group, :packages, :nodes, :name) }
let(:target_shas) { graphql_data_at(:group, :packages, :nodes, :metadata, :target_sha) }
let(:packages) { graphql_data_at(:group, :packages, :nodes) }
let(:fields) do
<<~QUERY
nodes {
#{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
metadata { #{query_graphql_fragment('ComposerMetadata')} }
}
QUERY
end
let(:query) do
graphql_query_for(
'group',
{ 'fullPath' => resource.full_path },
query_graphql_field('packages', {}, fields)
)
end
let(:resource_type) { :group }
it_behaves_like 'group and project packages query'
describe 'sorting and pagination' do
let_it_be(:ascending_packages) { [maven_package, package, composer_package, debian_package].map { |package| global_id_of(package)} }
let(:data_path) { [:group, :packages] }
before do
resource.add_reporter(current_user)
end
[:CREATED_ASC, :NAME_ASC, :VERSION_ASC, :TYPE_ASC].each do |order|
context "#{order}" do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { order }
let(:first_param) { 4 }
let(:expected_results) { ascending_packages }
end
end
end
[:CREATED_DESC, :NAME_DESC, :VERSION_DESC, :TYPE_DESC].each do |order|
context "#{order}" do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { order }
let(:first_param) { 4 }
let(:expected_results) { ascending_packages.reverse }
end
end
end
def pagination_query(params)
graphql_query_for(:group, { 'fullPath' => resource.full_path },
query_nodes(:packages, :id, include_pagination_info: true, args: params)
)
end
end
context 'with a batched query' do
let_it_be(:group_two_project) { create(:project, :repository, group: group_two) }
let_it_be(:group_one_package) { create(:npm_package, project: project1) }
let_it_be(:group_two_package) { create(:npm_package, project: group_two_project) }
let(:batch_query) do
<<~QUERY
{
......@@ -101,12 +38,7 @@ RSpec.describe 'getting a package list for a group' do
end
it 'returns an error for the second group and data for the first' do
expect(a_packages_names).to contain_exactly(
package.name,
maven_package.name,
debian_package.name,
composer_package.name
)
expect(a_packages_names).to contain_exactly(group_one_package.name)
expect_graphql_errors_to_include [/Packages can be requested only for one group at a time/]
expect(graphql_data_at(:b, :packages)).to be(nil)
end
......
......@@ -7,72 +7,10 @@ RSpec.describe 'getting a package list for a project' do
let_it_be(:resource) { create(:project, :repository) }
let_it_be(:current_user) { create(:user) }
let_it_be(:maven_package) { create(:maven_package, project: resource, name: 'tab', version: '4.0.0', created_at: 5.days.ago) }
let_it_be(:package) { create(:npm_package, project: resource, name: 'uab', version: '5.0.0', created_at: 4.days.ago) }
let_it_be(:composer_package) { create(:composer_package, project: resource, name: 'vab', version: '6.0.0', created_at: 3.days.ago) }
let_it_be(:debian_package) { create(:debian_package, project: resource, name: 'zab', version: '7.0.0', created_at: 2.days.ago) }
let_it_be(:composer_metadatum) do
create(:composer_metadatum, package: composer_package,
target_sha: 'afdeh',
composer_json: { name: 'x', type: 'y', license: 'z', version: 1 })
end
let_it_be(:project1) { resource }
let_it_be(:project2) { resource }
let(:package_names) { graphql_data_at(:project, :packages, :nodes, :name) }
let(:target_shas) { graphql_data_at(:project, :packages, :nodes, :metadata, :target_sha) }
let(:packages) { graphql_data_at(:project, :packages, :nodes) }
let(:fields) do
<<~QUERY
nodes {
#{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
metadata { #{query_graphql_fragment('ComposerMetadata')} }
}
QUERY
end
let(:query) do
graphql_query_for(
'project',
{ 'fullPath' => resource.full_path },
query_graphql_field('packages', {}, fields)
)
end
let(:resource_type) { :project }
it_behaves_like 'group and project packages query'
describe 'sorting and pagination' do
let_it_be(:ascending_packages) { [maven_package, package, composer_package, debian_package].map { |package| global_id_of(package)} }
let(:data_path) { [:project, :packages] }
before do
resource.add_reporter(current_user)
end
[:CREATED_ASC, :NAME_ASC, :VERSION_ASC, :TYPE_ASC].each do |order|
context "#{order}" do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { order }
let(:first_param) { 4 }
let(:expected_results) { ascending_packages }
end
end
end
[:CREATED_DESC, :NAME_DESC, :VERSION_DESC, :TYPE_DESC].each do |order|
context "#{order}" do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { order }
let(:first_param) { 4 }
let(:expected_results) { ascending_packages.reverse }
end
end
end
def pagination_query(params)
graphql_query_for(:project, { 'fullPath' => resource.full_path },
query_nodes(:packages, :id, include_pagination_info: true, args: params)
)
end
end
end
# frozen_string_literal: true
RSpec.shared_examples 'group and projects packages resolver' do
context 'without sort' do
let_it_be(:npm_package) { create(:package, project: project) }
it { is_expected.to contain_exactly(npm_package) }
end
context 'with sorting and filtering' do
let_it_be(:conan_package) do
create(:conan_package, name: 'bar', project: project, created_at: 1.day.ago, version: "1.0.0", status: 'default')
end
let_it_be(:maven_package) do
create(:maven_package, name: 'foo', project: project, created_at: 1.hour.ago, version: "2.0.0", status: 'error')
end
let_it_be(:repository3) do
create(:maven_package, name: 'baz', project: project, created_at: 1.minute.ago, version: nil)
end
[:created_desc, :name_desc, :version_desc, :type_asc].each do |order|
context "#{order}" do
let(:args) { { sort: order } }
it { is_expected.to eq([maven_package, conan_package]) }
end
end
[:created_asc, :name_asc, :version_asc, :type_desc].each do |order|
context "#{order}" do
let(:args) { { sort: order } }
it { is_expected.to eq([conan_package, maven_package]) }
end
end
context 'filter by package_name' do
let(:args) { { package_name: 'bar', sort: :created_desc } }
it { is_expected.to eq([conan_package]) }
end
context 'filter by package_type' do
let(:args) { { package_type: 'conan', sort: :created_desc } }
it { is_expected.to eq([conan_package]) }
end
context 'filter by status' do
let(:args) { { status: 'error', sort: :created_desc } }
it { is_expected.to eq([maven_package]) }
end
context 'include_versionless' do
let(:args) { { include_versionless: true, sort: :created_desc } }
it { is_expected.to include(repository3) }
end
end
end
......@@ -3,6 +3,38 @@
RSpec.shared_examples 'group and project packages query' do
include GraphqlHelpers
let_it_be(:versionaless_package) { create(:maven_package, project: project1, version: nil) }
let_it_be(:maven_package) { create(:maven_package, project: project1, name: 'tab', version: '4.0.0', created_at: 5.days.ago) }
let_it_be(:package) { create(:npm_package, project: project1, name: 'uab', version: '5.0.0', created_at: 4.days.ago) }
let_it_be(:composer_package) { create(:composer_package, project: project2, name: 'vab', version: '6.0.0', created_at: 3.days.ago) }
let_it_be(:debian_package) { create(:debian_package, project: project2, name: 'zab', version: '7.0.0', created_at: 2.days.ago) }
let_it_be(:composer_metadatum) do
create(:composer_metadatum, package: composer_package,
target_sha: 'afdeh',
composer_json: { name: 'x', type: 'y', license: 'z', version: 1 })
end
let(:package_names) { graphql_data_at(resource_type, :packages, :nodes, :name) }
let(:target_shas) { graphql_data_at(resource_type, :packages, :nodes, :metadata, :target_sha) }
let(:packages) { graphql_data_at(resource_type, :packages, :nodes) }
let(:fields) do
<<~QUERY
nodes {
#{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
metadata { #{query_graphql_fragment('ComposerMetadata')} }
}
QUERY
end
let(:query) do
graphql_query_for(
resource_type,
{ 'fullPath' => resource.full_path },
query_graphql_field('packages', {}, fields)
)
end
context 'when user has access to the resource' do
before do
resource.add_reporter(current_user)
......@@ -48,4 +80,101 @@ RSpec.shared_examples 'group and project packages query' do
expect(packages).to be_nil
end
end
describe 'sorting and pagination' do
let_it_be(:ascending_packages) { [maven_package, package, composer_package, debian_package].map { |package| global_id_of(package)} }
let(:data_path) { [resource_type, :packages] }
before do
resource.add_reporter(current_user)
end
[:CREATED_ASC, :NAME_ASC, :VERSION_ASC, :TYPE_ASC].each do |order|
context "#{order}" do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { order }
let(:first_param) { 4 }
let(:expected_results) { ascending_packages }
end
end
end
[:CREATED_DESC, :NAME_DESC, :VERSION_DESC, :TYPE_DESC].each do |order|
context "#{order}" do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { order }
let(:first_param) { 4 }
let(:expected_results) { ascending_packages.reverse }
end
end
end
context 'with an invalid sort' do
let(:query) do
graphql_query_for(
resource_type,
{ 'fullPath' => resource.full_path },
query_nodes(:packages, :name, args: { sort: :WRONG_ORDER })
)
end
before do
post_graphql(query, current_user: current_user)
end
it 'throws an error' do
expect_graphql_errors_to_include(/Argument \'sort\' on Field \'packages\' has an invalid value/)
end
end
def pagination_query(params)
graphql_query_for(resource_type, { 'fullPath' => resource.full_path },
query_nodes(:packages, :id, include_pagination_info: true, args: params)
)
end
end
describe 'filtering' do
subject { packages }
let(:query) do
graphql_query_for(
resource_type,
{ 'fullPath' => resource.full_path },
query_nodes(:packages, :name, args: params)
)
end
before do
resource.add_reporter(current_user)
post_graphql(query, current_user: current_user)
end
context 'package_name' do
let(:params) { { package_name: maven_package.name } }
it { is_expected.to contain_exactly({ "name" => maven_package.name }) }
end
context 'package_type' do
let(:params) { { package_type: :COMPOSER } }
it { is_expected.to contain_exactly({ "name" => composer_package.name }) }
end
context 'status' do
let_it_be(:errored_package) { create(:maven_package, project: project1, status: 'error') }
let(:params) { { status: :ERROR } }
it { is_expected.to contain_exactly({ "name" => errored_package.name }) }
end
context 'include_versionless' do
let(:params) { { include_versionless: true } }
it { is_expected.to include({ "name" => versionaless_package.name }) }
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