Commit 5229a766 authored by Vitali Tatarintev's avatar Vitali Tatarintev

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

Add composer details GraphQL type and query

See merge request gitlab-org/gitlab!51059
parents d89d8429 4646fa8e
# frozen_string_literal: true
module Resolvers
# No return types defined because they can be different.
# rubocop: disable Graphql/ResolverType
class PackageDetailsResolver < BaseResolver
argument :id, ::Types::GlobalIDType[::Packages::Package],
required: true,
description: 'The global ID of the package.'
def resolve(id:)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::Packages::Package].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
end
end
......@@ -2,7 +2,7 @@
module Resolvers
class PackagesResolver < BaseResolver
type Types::PackageType, null: true
type Types::Packages::PackageType, null: true
def resolve(**args)
return unless packages_available?
......
# frozen_string_literal: true
module Types
class PackageType < BaseObject
graphql_name 'Package'
description 'Represents a package'
authorize :read_package
field :id, GraphQL::ID_TYPE, null: false, description: 'The ID of the package'
field :name, GraphQL::STRING_TYPE, null: false, description: 'The name of the package'
field :created_at, Types::TimeType, null: false, description: 'The created date'
field :updated_at, Types::TimeType, null: false, description: 'The update date'
field :version, GraphQL::STRING_TYPE, null: true, description: 'The version of the package'
field :package_type, Types::PackageTypeEnum, null: false, description: 'The type of the package'
end
end
# frozen_string_literal: true
module Types
class PackageTypeEnum < BaseEnum
PACKAGE_TYPE_NAMES = {
pypi: 'PyPI',
npm: 'NPM'
}.freeze
::Packages::Package.package_types.keys.each do |package_type|
type_name = PACKAGE_TYPE_NAMES.fetch(package_type.to_sym, package_type.capitalize)
value package_type.to_s.upcase, "Packages from the #{type_name} package manager", value: package_type.to_s
end
end
end
# frozen_string_literal: true
module Types
module Packages
module Composer
class DetailsType < Types::Packages::PackageType
graphql_name 'PackageComposerDetails'
description 'Details of a Composer package'
authorize :read_package
field :composer_metadatum, Types::Packages::Composer::MetadatumType, null: false, description: 'The Composer metadatum.'
end
end
end
end
# frozen_string_literal: true
module Types
module Packages
module Composer
# rubocop: disable Graphql/AuthorizeTypes
class JsonType < BaseObject
graphql_name 'PackageComposerJsonType'
description 'Represents a composer JSON file'
field :name, GraphQL::STRING_TYPE, null: true, description: 'The name set in the Composer JSON file.'
field :type, GraphQL::STRING_TYPE, null: true, description: 'The type set in the Composer JSON file.'
field :license, GraphQL::STRING_TYPE, null: true, description: 'The license set in the Composer JSON file.'
field :version, GraphQL::STRING_TYPE, null: true, description: 'The version set in the Composer JSON file.'
end
end
end
end
# frozen_string_literal: true
module Types
module Packages
module Composer
class MetadatumType < BaseObject
graphql_name 'PackageComposerMetadatumType'
description 'Composer metadatum'
authorize :read_package
field :target_sha, GraphQL::STRING_TYPE, null: false, description: 'Target SHA of the package.'
field :composer_json, Types::Packages::Composer::JsonType, null: false, description: 'Data of the Composer JSON file.'
end
end
end
end
# frozen_string_literal: true
module Types
module Packages
class PackageTagType < BaseObject
graphql_name 'PackageTag'
description 'Represents a package tag'
authorize :read_package
field :id, GraphQL::ID_TYPE, null: false, description: 'The ID of the tag.'
field :name, GraphQL::STRING_TYPE, null: false, description: 'The name of the tag.'
field :created_at, Types::TimeType, null: false, description: 'The created date.'
field :updated_at, Types::TimeType, null: false, description: 'The updated date.'
end
end
end
# frozen_string_literal: true
module Types
module Packages
class PackageType < BaseObject
graphql_name 'Package'
description 'Represents a package in the Package Registry'
authorize :read_package
field :id, GraphQL::ID_TYPE, null: false, description: 'The ID of the package.'
field :name, GraphQL::STRING_TYPE, null: false, description: 'The name of the package.'
field :created_at, Types::TimeType, null: false, description: 'The created date.'
field :updated_at, Types::TimeType, null: false, description: 'The updated date.'
field :version, GraphQL::STRING_TYPE, null: true, description: 'The version of the package.'
field :package_type, Types::Packages::PackageTypeEnum, null: false, description: 'The type of the package.'
field :tags, Types::Packages::PackageTagType.connection_type, null: true, description: 'The package tags.'
field :project, Types::ProjectType, null: false, description: 'Project where the package is stored.'
field :pipelines, Types::Ci::PipelineType.connection_type, null: true, description: 'Pipelines that built the package.'
field :versions, Types::Packages::PackageType.connection_type, null: true, description: 'The other versions of the package.'
def project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
end
end
end
end
# frozen_string_literal: true
module Types
module Packages
class PackageTypeEnum < BaseEnum
PACKAGE_TYPE_NAMES = {
pypi: 'PyPI',
npm: 'NPM'
}.freeze
::Packages::Package.package_types.keys.each do |package_type|
type_name = PACKAGE_TYPE_NAMES.fetch(package_type.to_sym, package_type.capitalize)
value package_type.to_s.upcase, "Packages from the #{type_name} package manager", value: package_type.to_s
end
end
end
end
......@@ -175,7 +175,7 @@ module Types
description: 'A single issue of the project',
resolver: Resolvers::IssuesResolver.single
field :packages, Types::PackageType.connection_type, null: true,
field :packages, Types::Packages::PackageType.connection_type, null: true,
description: 'Packages of the project',
resolver: Resolvers::PackagesResolver
......
......@@ -58,6 +58,11 @@ module Types
argument :id, ::Types::GlobalIDType[::ContainerRepository], required: true, description: 'The global ID of the container repository'
end
field :package_composer_details, Types::Packages::Composer::DetailsType,
null: true,
description: 'Find a composer package',
resolver: Resolvers::PackageDetailsResolver
field :user, Types::UserType,
null: true,
description: 'Find a user',
......
# frozen_string_literal: true
module Packages
module Composer
class MetadatumPolicy < BasePolicy
delegate { @subject.package }
end
end
end
# frozen_string_literal: true
module Packages
class TagPolicy < BasePolicy
delegate { @subject.package }
end
end
---
title: Add composer details GraphQL type and query
merge_request: 51059
author:
type: added
......@@ -16521,38 +16521,278 @@ input OncallUserInputType {
}
"""
Represents a package
Represents a package in the Package Registry
"""
type Package {
"""
The created date
The created date.
"""
createdAt: Time!
"""
The ID of the package
The ID of the package.
"""
id: ID!
"""
The name of the package
The name of the package.
"""
name: String!
"""
The type of the package
The type of the package.
"""
packageType: PackageTypeEnum!
"""
The update date
Pipelines that built the package.
"""
pipelines(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): PipelineConnection
"""
Project where the package is stored.
"""
project: Project!
"""
The package tags.
"""
tags(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): PackageTagConnection
"""
The updated date.
"""
updatedAt: Time!
"""
The version of the package
The version of the package.
"""
version: String
"""
The other versions of the package.
"""
versions(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): PackageConnection
}
"""
Details of a Composer package
"""
type PackageComposerDetails {
"""
The Composer metadatum.
"""
composerMetadatum: PackageComposerMetadatumType!
"""
The created date.
"""
createdAt: Time!
"""
The ID of the package.
"""
id: ID!
"""
The name of the package.
"""
name: String!
"""
The type of the package.
"""
packageType: PackageTypeEnum!
"""
Pipelines that built the package.
"""
pipelines(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): PipelineConnection
"""
Project where the package is stored.
"""
project: Project!
"""
The package tags.
"""
tags(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): PackageTagConnection
"""
The updated date.
"""
updatedAt: Time!
"""
The version of the package.
"""
version: String
"""
The other versions of the package.
"""
versions(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): PackageConnection
}
"""
Represents a composer JSON file
"""
type PackageComposerJsonType {
"""
The license set in the Composer JSON file.
"""
license: String
"""
The name set in the Composer JSON file.
"""
name: String
"""
The type set in the Composer JSON file.
"""
type: String
"""
The version set in the Composer JSON file.
"""
version: String
}
"""
Composer metadatum
"""
type PackageComposerMetadatumType {
"""
Data of the Composer JSON file.
"""
composerJson: PackageComposerJsonType!
"""
Target SHA of the package.
"""
targetSha: String!
}
"""
......@@ -16686,6 +16926,66 @@ type PackageSettings {
mavenDuplicatesAllowed: Boolean!
}
"""
Represents a package tag
"""
type PackageTag {
"""
The created date.
"""
createdAt: Time!
"""
The ID of the tag.
"""
id: ID!
"""
The name of the tag.
"""
name: String!
"""
The updated date.
"""
updatedAt: Time!
}
"""
The connection type for PackageTag.
"""
type PackageTagConnection {
"""
A list of edges.
"""
edges: [PackageTagEdge]
"""
A list of nodes.
"""
nodes: [PackageTag]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type PackageTagEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: PackageTag
}
enum PackageTypeEnum {
"""
Packages from the Composer package manager
......@@ -16733,6 +17033,11 @@ enum PackageTypeEnum {
PYPI
}
"""
Identifier of Packages::Package
"""
scalar PackagesPackageID
"""
Information about pagination in a connection.
"""
......@@ -19867,6 +20172,16 @@ type Query {
fullPath: ID!
): Namespace
"""
Find a composer package
"""
packageComposerDetails(
"""
The global ID of the package.
"""
id: PackagesPackageID!
): PackageComposerDetails
"""
Find a project
"""
......
......@@ -2496,16 +2496,58 @@ Autogenerated return type of OncallScheduleUpdate.
### Package
Represents a package.
Represents a package in the Package Registry.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `createdAt` | Time! | The created date |
| `id` | ID! | The ID of the package |
| `name` | String! | The name of the package |
| `packageType` | PackageTypeEnum! | The type of the package |
| `updatedAt` | Time! | The update date |
| `version` | String | The version of the package |
| `createdAt` | Time! | The created date. |
| `id` | ID! | The ID of the package. |
| `name` | String! | The name of the package. |
| `packageType` | PackageTypeEnum! | The type of the package. |
| `pipelines` | PipelineConnection | Pipelines that built the package. |
| `project` | Project! | Project where the package is stored. |
| `tags` | PackageTagConnection | The package tags. |
| `updatedAt` | Time! | The updated date. |
| `version` | String | The version of the package. |
| `versions` | PackageConnection | The other versions of the package. |
### PackageComposerDetails
Details of a Composer package.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `composerMetadatum` | PackageComposerMetadatumType! | The Composer metadatum. |
| `createdAt` | Time! | The created date. |
| `id` | ID! | The ID of the package. |
| `name` | String! | The name of the package. |
| `packageType` | PackageTypeEnum! | The type of the package. |
| `pipelines` | PipelineConnection | Pipelines that built the package. |
| `project` | Project! | Project where the package is stored. |
| `tags` | PackageTagConnection | The package tags. |
| `updatedAt` | Time! | The updated date. |
| `version` | String | The version of the package. |
| `versions` | PackageConnection | The other versions of the package. |
### PackageComposerJsonType
Represents a composer JSON file.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `license` | String | The license set in the Composer JSON file. |
| `name` | String | The name set in the Composer JSON file. |
| `type` | String | The type set in the Composer JSON file. |
| `version` | String | The version set in the Composer JSON file. |
### PackageComposerMetadatumType
Composer metadatum.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `composerJson` | PackageComposerJsonType! | Data of the Composer JSON file. |
| `targetSha` | String! | Target SHA of the package. |
### PackageFileRegistry
......@@ -2531,6 +2573,17 @@ Namespace-level Package Registry settings.
| `mavenDuplicateExceptionRegex` | UntrustedRegexp | When maven_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. |
| `mavenDuplicatesAllowed` | Boolean! | Indicates whether duplicate Maven packages are allowed for this namespace. |
### PackageTag
Represents a package tag.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `createdAt` | Time! | The created date. |
| `id` | ID! | The ID of the tag. |
| `name` | String! | The name of the tag. |
| `updatedAt` | Time! | The updated date. |
### PageInfo
Information about pagination in a connection..
......
{
"type": "object",
"allOf": [{ "$ref": "./package_details.json" }],
"properties": {
"target_sha": {
"type": "string"
},
"composer_json": {
"type": "object"
}
}
}
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"updatedAt": {
"type": "string"
},
"version": {
"type": ["string", "null"]
},
"package_type": {
"type": ["string"],
"enum": ["MAVEN", "NPM", "CONAN", "NUGET", "PYPI", "COMPOSER", "GENERIC", "GOLANG", "DEBIAN"]
},
"tags": {
"type": "object"
},
"project": {
"type": "object"
},
"pipelines": {
"type": "object"
},
"versions": {
"type": "object"
}
}
}
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::PackageDetailsResolver do
include GraphqlHelpers
let_it_be_with_reload(:project) { create(:project) }
let_it_be(:user) { project.owner }
let_it_be(:package) { create(:composer_package, project: project) }
describe '#resolve' do
let(:args) do
{ id: package.to_global_id.to_s }
end
subject { resolve(described_class, ctx: { current_user: user }, args: args).sync }
it { is_expected.to eq(package) }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageComposerDetails'] do
it { expect(described_class.graphql_name).to eq('PackageComposerDetails') }
it 'includes all the package fields' do
expected_fields = %w[
id name version created_at updated_at package_type tags project pipelines versions
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
it 'includes composer specific files' do
expected_fields = %w[
composer_metadatum
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageComposerJsonType'] do
it { expect(described_class.graphql_name).to eq('PackageComposerJsonType') }
it 'includes composer json files' do
expected_fields = %w[
name type license version
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageComposerMetadatumType'] do
it { expect(described_class.graphql_name).to eq('PackageComposerMetadatumType') }
it 'includes composer metadatum fields' do
expected_fields = %w[
target_sha composer_json
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end
......@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['Package'] do
it 'includes all the package fields' do
expected_fields = %w[
id name version created_at updated_at package_type
id name version created_at updated_at package_type tags project pipelines versions
]
expect(described_class).to include_graphql_fields(*expected_fields)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageTag'] do
it { expect(described_class.graphql_name).to eq('PackageTag') }
it 'includes all the package tag fields' do
expected_fields = %w[
id name created_at updated_at
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end
......@@ -94,4 +94,10 @@ RSpec.describe GitlabSchema.types['Query'] do
it { is_expected.to have_graphql_type(Types::ContainerRepositoryDetailsType) }
end
describe 'package_composer_details field' do
subject { described_class.fields['packageComposerDetails'] }
it { is_expected.to have_graphql_type(Types::Packages::Composer::DetailsType) }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'package composer details' do
using RSpec::Parameterized::TableSyntax
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:package) { create(:composer_package, project: project) }
let_it_be(:composer_metadatum) do
# we are forced to manually create the metadatum, without using the factory to force the sha to be a string
# and avoid an error where gitaly can't find the repository
create(:composer_metadatum, package: package, target_sha: 'foo_sha', composer_json: { name: 'name', type: 'type', license: 'license', version: 1 })
end
let(:query) do
graphql_query_for(
'packageComposerDetails',
{ id: package_global_id },
all_graphql_fields_for('PackageComposerDetails', max_depth: 2)
)
end
let(:user) { project.owner }
let(:package_global_id) { package.to_global_id.to_s }
let(:package_composer_details_response) { graphql_data.dig('packageComposerDetails') }
subject { post_graphql(query, current_user: user) }
it_behaves_like 'a working graphql query' do
before do
subject
end
it 'matches the JSON schema' do
expect(package_composer_details_response).to match_schema('graphql/packages/package_composer_details')
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