Commit b51b57dc authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch 'ajk-graphql-packages' into 'master'

Packages to use composition, not inheritance [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!52131
parents b6dce9d0 8e5772f8
# frozen_string_literal: true # frozen_string_literal: true
module Resolvers module Resolvers
# No return types defined because they can be different.
# rubocop: disable Graphql/ResolverType
class PackageDetailsResolver < BaseResolver class PackageDetailsResolver < BaseResolver
type ::Types::Packages::PackageType, null: true
argument :id, ::Types::GlobalIDType[::Packages::Package], argument :id, ::Types::GlobalIDType[::Packages::Package],
required: true, required: true,
description: 'The global ID of the package.' description: 'The global ID of the package.'
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module Resolvers module Resolvers
class PackagesResolver < BaseResolver class PackagesResolver < BaseResolver
type Types::Packages::PackageType, null: true type Types::Packages::PackageType.connection_type, null: true
def resolve(**args) def resolve(**args)
return unless packages_available? return unless packages_available?
......
# 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
...@@ -4,8 +4,8 @@ module Types ...@@ -4,8 +4,8 @@ module Types
module Packages module Packages
module Composer module Composer
class MetadatumType < BaseObject class MetadatumType < BaseObject
graphql_name 'PackageComposerMetadatumType' graphql_name 'ComposerMetadata'
description 'Composer metadatum' description 'Composer metadata'
authorize :read_package authorize :read_package
......
# frozen_string_literal: true
module Types
module Packages
class MetadataType < BaseUnion
graphql_name 'PackageMetadata'
description 'Represents metadata associated with a Package'
possible_types ::Types::Packages::Composer::MetadatumType
def self.resolve_type(object, context)
case object
when ::Packages::Composer::Metadatum
::Types::Packages::Composer::MetadatumType
else
# NOTE: This method must be kept in sync with `PackageWithoutVersionsType#metadata`,
# which must never produce data that this discriminator cannot handle.
raise 'Unsupported metadata type'
end
end
end
end
end
...@@ -2,26 +2,13 @@ ...@@ -2,26 +2,13 @@
module Types module Types
module Packages module Packages
class PackageType < BaseObject class PackageType < PackageWithoutVersionsType
graphql_name 'Package' graphql_name 'Package'
description 'Represents a package in the Package Registry' description 'Represents a package in the Package Registry'
authorize :read_package authorize :read_package
field :id, GraphQL::ID_TYPE, null: false, description: 'The ID of the package.' field :versions, ::Types::Packages::PackageWithoutVersionsType.connection_type, null: true,
field :name, GraphQL::STRING_TYPE, null: false, description: 'The name of the package.' description: 'The other versions 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 end
end end
# frozen_string_literal: true
module Types
module Packages
class PackageWithoutVersionsType < ::Types::BaseObject
graphql_name 'PackageWithoutVersions'
description 'Represents a version of a package in the Package Registry'
authorize :read_package
field :id, ::Types::GlobalIDType[::Packages::Package], null: false,
description: 'ID of the package.'
field :name, GraphQL::STRING_TYPE, null: false, description: 'Name of the package.'
field :created_at, Types::TimeType, null: false, description: 'Date of creation.'
field :updated_at, Types::TimeType, null: false, description: 'Date of most recent update.'
field :version, GraphQL::STRING_TYPE, null: true, description: 'Version string.'
field :package_type, Types::Packages::PackageTypeEnum, null: false, description: 'Package type.'
field :tags, Types::Packages::PackageTagType.connection_type, null: true, description: '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 :metadata, Types::Packages::MetadataType, null: true,
description: 'Package metadata.'
def project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
end
# NOTE: This method must be kept in sync with the union
# type: `Types::Packages::MetadataType`.
#
# `Types::Packages::MetadataType.resolve_type(metadata, ctx)` must never raise.
def metadata
case object.package_type
when 'composer'
object.composer_metadatum
else
nil
end
end
end
end
end
...@@ -179,7 +179,7 @@ module Types ...@@ -179,7 +179,7 @@ module Types
description: 'A single issue of the project', description: 'A single issue of the project',
resolver: Resolvers::IssuesResolver.single resolver: Resolvers::IssuesResolver.single
field :packages, Types::Packages::PackageType.connection_type, null: true, field :packages,
description: 'Packages of the project', description: 'Packages of the project',
resolver: Resolvers::PackagesResolver resolver: Resolvers::PackagesResolver
......
...@@ -58,9 +58,8 @@ module Types ...@@ -58,9 +58,8 @@ module Types
argument :id, ::Types::GlobalIDType[::ContainerRepository], required: true, description: 'The global ID of the container repository' argument :id, ::Types::GlobalIDType[::ContainerRepository], required: true, description: 'The global ID of the container repository'
end end
field :package_composer_details, Types::Packages::Composer::DetailsType, field :package,
null: true, description: 'Find a package',
description: 'Find a composer package',
resolver: Resolvers::PackageDetailsResolver resolver: Resolvers::PackageDetailsResolver
field :user, Types::UserType, field :user, Types::UserType,
......
...@@ -94,7 +94,6 @@ module Security ...@@ -94,7 +94,6 @@ module Security
def gitlab_ci_yml_attributes def gitlab_ci_yml_attributes
@gitlab_ci_yml_attributes ||= begin @gitlab_ci_yml_attributes ||= begin
config_content = @project.repository.blob_data_at(@project.repository.root_ref_sha, ci_config_file) config_content = @project.repository.blob_data_at(@project.repository.root_ref_sha, ci_config_file)
return {} unless config_content return {} unless config_content
build_sast_attributes(config_content) build_sast_attributes(config_content)
......
---
title: 'Breaking change: Prevent mutual recursion in GraphQL Package'
merge_request: 52131
author:
type: fixed
...@@ -3879,6 +3879,21 @@ Identifier of ComplianceManagement::Framework. ...@@ -3879,6 +3879,21 @@ Identifier of ComplianceManagement::Framework.
""" """
scalar ComplianceManagementFrameworkID scalar ComplianceManagementFrameworkID
"""
Composer metadata
"""
type ComposerMetadata {
"""
Data of the Composer JSON file.
"""
composerJson: PackageComposerJsonType!
"""
Target SHA of the package.
"""
targetSha: String!
}
""" """
Autogenerated input type of ConfigureSast Autogenerated input type of ConfigureSast
""" """
...@@ -16880,142 +16895,27 @@ Represents a package in the Package Registry ...@@ -16880,142 +16895,27 @@ Represents a package in the Package Registry
""" """
type Package { type Package {
""" """
The created date. Date of creation.
""" """
createdAt: Time! createdAt: Time!
""" """
The ID of the package. 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( id: PackagesPackageID!
"""
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. Package metadata.
""" """
project: Project! metadata: PackageMetadata
""" """
The package tags. Name of the package.
"""
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
}
"""
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! name: String!
""" """
The type of the package. Package type.
""" """
packageType: PackageTypeEnum! packageType: PackageTypeEnum!
...@@ -17050,7 +16950,7 @@ type PackageComposerDetails { ...@@ -17050,7 +16950,7 @@ type PackageComposerDetails {
project: Project! project: Project!
""" """
The package tags. Package tags.
""" """
tags( tags(
""" """
...@@ -17075,12 +16975,12 @@ type PackageComposerDetails { ...@@ -17075,12 +16975,12 @@ type PackageComposerDetails {
): PackageTagConnection ): PackageTagConnection
""" """
The updated date. Date of most recent update.
""" """
updatedAt: Time! updatedAt: Time!
""" """
The version of the package. Version string.
""" """
version: String version: String
...@@ -17107,7 +17007,7 @@ type PackageComposerDetails { ...@@ -17107,7 +17007,7 @@ type PackageComposerDetails {
Returns the last _n_ elements from the list. Returns the last _n_ elements from the list.
""" """
last: Int last: Int
): PackageConnection ): PackageWithoutVersionsConnection
} }
""" """
...@@ -17135,21 +17035,6 @@ type PackageComposerJsonType { ...@@ -17135,21 +17035,6 @@ type PackageComposerJsonType {
version: String version: String
} }
"""
Composer metadatum
"""
type PackageComposerMetadatumType {
"""
Data of the Composer JSON file.
"""
composerJson: PackageComposerJsonType!
"""
Target SHA of the package.
"""
targetSha: String!
}
""" """
The connection type for Package. The connection type for Package.
""" """
...@@ -17265,6 +17150,11 @@ type PackageFileRegistryEdge { ...@@ -17265,6 +17150,11 @@ type PackageFileRegistryEdge {
node: PackageFileRegistry node: PackageFileRegistry
} }
"""
Represents metadata associated with a Package
"""
union PackageMetadata = ComposerMetadata
""" """
Namespace-level Package Registry settings Namespace-level Package Registry settings
""" """
...@@ -17388,6 +17278,136 @@ enum PackageTypeEnum { ...@@ -17388,6 +17278,136 @@ enum PackageTypeEnum {
PYPI PYPI
} }
"""
Represents a version of a package in the Package Registry
"""
type PackageWithoutVersions {
"""
Date of creation.
"""
createdAt: Time!
"""
ID of the package.
"""
id: PackagesPackageID!
"""
Package metadata.
"""
metadata: PackageMetadata
"""
Name of the package.
"""
name: String!
"""
Package type.
"""
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!
"""
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
"""
Date of most recent update.
"""
updatedAt: Time!
"""
Version string.
"""
version: String
}
"""
The connection type for PackageWithoutVersions.
"""
type PackageWithoutVersionsConnection {
"""
A list of edges.
"""
edges: [PackageWithoutVersionsEdge]
"""
A list of nodes.
"""
nodes: [PackageWithoutVersions]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type PackageWithoutVersionsEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: PackageWithoutVersions
}
""" """
Identifier of Packages::Package. Identifier of Packages::Package.
""" """
...@@ -20538,14 +20558,14 @@ type Query { ...@@ -20538,14 +20558,14 @@ type Query {
): Namespace ): Namespace
""" """
Find a composer package Find a package
""" """
packageComposerDetails( package(
""" """
The global ID of the package. The global ID of the package.
""" """
id: PackagesPackageID! id: PackagesPackageID!
): PackageComposerDetails ): Package
""" """
Find a project Find a project
......
...@@ -593,6 +593,15 @@ Represents a ComplianceFramework associated with a Project. ...@@ -593,6 +593,15 @@ Represents a ComplianceFramework associated with a Project.
| `name` | String! | Name of the compliance framework | | `name` | String! | Name of the compliance framework |
| `pipelineConfigurationFullPath` | String | Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`. | | `pipelineConfigurationFullPath` | String | Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`. |
### ComposerMetadata
Composer metadata.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `composerJson` | PackageComposerJsonType! | Data of the Composer JSON file. |
| `targetSha` | String! | Target SHA of the package. |
### ConfigureSastPayload ### ConfigureSastPayload
Autogenerated return type of ConfigureSast. Autogenerated return type of ConfigureSast.
...@@ -2541,34 +2550,17 @@ Represents a package in the Package Registry. ...@@ -2541,34 +2550,17 @@ Represents a package in the Package Registry.
| Field | Type | Description | | Field | Type | Description |
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `createdAt` | Time! | The created date. | | `createdAt` | Time! | Date of creation. |
| `id` | ID! | The ID of the package. | | `id` | PackagesPackageID! | ID of the package. |
| `name` | String! | The name of the package. | | `metadata` | PackageMetadata | Package metadata. |
| `packageType` | PackageTypeEnum! | The type of the package. | | `name` | String! | Name of the package. |
| `pipelines` | PipelineConnection | Pipelines that built the package. | | `packageType` | PackageTypeEnum! | Package type. |
| `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. | | `pipelines` | PipelineConnection | Pipelines that built the package. |
| `project` | Project! | Project where the package is stored. | | `project` | Project! | Project where the package is stored. |
| `tags` | PackageTagConnection | The package tags. | | `tags` | PackageTagConnection | Package tags. |
| `updatedAt` | Time! | The updated date. | | `updatedAt` | Time! | Date of most recent update. |
| `version` | String | The version of the package. | | `version` | String | Version string. |
| `versions` | PackageConnection | The other versions of the package. | | `versions` | PackageWithoutVersionsConnection | The other versions of the package. |
### PackageComposerJsonType ### PackageComposerJsonType
...@@ -2581,15 +2573,6 @@ Represents a composer JSON file. ...@@ -2581,15 +2573,6 @@ Represents a composer JSON file.
| `type` | String | The type 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. | | `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 ### PackageFileRegistry
Represents the Geo sync and verification state of a package file. Represents the Geo sync and verification state of a package file.
...@@ -2625,6 +2608,23 @@ Represents a package tag. ...@@ -2625,6 +2608,23 @@ Represents a package tag.
| `name` | String! | The name of the tag. | | `name` | String! | The name of the tag. |
| `updatedAt` | Time! | The updated date. | | `updatedAt` | Time! | The updated date. |
### PackageWithoutVersions
Represents a version of a package in the Package Registry.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `createdAt` | Time! | Date of creation. |
| `id` | PackagesPackageID! | ID of the package. |
| `metadata` | PackageMetadata | Package metadata. |
| `name` | String! | Name of the package. |
| `packageType` | PackageTypeEnum! | Package type. |
| `pipelines` | PipelineConnection | Pipelines that built the package. |
| `project` | Project! | Project where the package is stored. |
| `tags` | PackageTagConnection | Package tags. |
| `updatedAt` | Time! | Date of most recent update. |
| `version` | String | Version string. |
### PageInfo ### PageInfo
Information about pagination in a connection.. Information about pagination in a connection..
......
{
"type": "object",
"allOf": [{ "$ref": "./package_details.json" }],
"properties": {
"target_sha": {
"type": "string"
},
"composer_json": {
"type": "object"
}
}
}
{
"type": "object",
"additionalProperties": false,
"required": ["targetSha", "composerJson"],
"properties": {
"targetSha": {
"type": "string"
},
"composerJson": {
"type": "object",
"additionalProperties": false,
"required": ["name", "type", "license", "version"],
"properties": {
"name": { "type": "string" },
"type": { "type": "string" },
"license": { "type": "string" },
"version": { "type": "string" }
}
}
}
}
{ {
"type": "object", "type": "object",
"additionalProperties": false,
"required": [
"id", "name", "createdAt", "updatedAt", "version", "packageType",
"project", "tags", "pipelines", "versions", "metadata"
],
"properties": { "properties": {
"id": { "id": {
"type": "string" "type": "string"
...@@ -16,21 +21,46 @@ ...@@ -16,21 +21,46 @@
"version": { "version": {
"type": ["string", "null"] "type": ["string", "null"]
}, },
"package_type": { "packageType": {
"type": ["string"], "type": ["string"],
"enum": ["MAVEN", "NPM", "CONAN", "NUGET", "PYPI", "COMPOSER", "GENERIC", "GOLANG", "DEBIAN"] "enum": ["MAVEN", "NPM", "CONAN", "NUGET", "PYPI", "COMPOSER", "GENERIC", "GOLANG", "DEBIAN"]
}, },
"tags": { "tags": {
"type": "object" "type": "object",
"additionalProperties": false,
"properties": {
"pageInfo": { "type": "object" },
"edges": { "type": "array" },
"nodes": { "type": "array" }
}
}, },
"project": { "project": {
"type": "object" "type": "object"
}, },
"pipelines": { "pipelines": {
"type": "object" "type": "object",
"additionalProperties": false,
"properties": {
"pageInfo": { "type": "object" },
"count": { "type": "integer" },
"edges": { "type": "array" },
"nodes": { "type": "array" }
}
}, },
"versions": { "versions": {
"type": "object" "type": "object",
"additionalProperties": false,
"properties": {
"pageInfo": { "type": "object" },
"edges": { "type": "array" },
"nodes": { "type": "array" }
}
},
"metadata": {
"anyOf": [
{ "$ref": "./package_composer_metadata.json" },
{ "type": "null" }
]
} }
} }
} }
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Resolvers::PackageDetailsResolver do RSpec.describe Resolvers::PackageDetailsResolver do
include GraphqlHelpers include GraphqlHelpers
include ::Gitlab::Graphql::Laziness
let_it_be_with_reload(:project) { create(:project) } let_it_be_with_reload(:project) { create(:project) }
let_it_be(:user) { project.owner } let_it_be(:user) { project.owner }
...@@ -11,10 +12,10 @@ RSpec.describe Resolvers::PackageDetailsResolver do ...@@ -11,10 +12,10 @@ RSpec.describe Resolvers::PackageDetailsResolver do
describe '#resolve' do describe '#resolve' do
let(:args) do let(:args) do
{ id: package.to_global_id.to_s } { id: global_id_of(package) }
end end
subject { resolve(described_class, ctx: { current_user: user }, args: args).sync } subject { force(resolve(described_class, ctx: { current_user: user }, args: args)) }
it { is_expected.to eq(package) } it { is_expected.to eq(package) }
end end
......
...@@ -2,9 +2,7 @@ ...@@ -2,9 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageComposerMetadatumType'] do RSpec.describe GitlabSchema.types['ComposerMetadata'] do
it { expect(described_class.graphql_name).to eq('PackageComposerMetadatumType') }
it 'includes composer metadatum fields' do it 'includes composer metadatum fields' do
expected_fields = %w[ expected_fields = %w[
target_sha composer_json target_sha composer_json
......
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['Package'] do RSpec.describe GitlabSchema.types['Package'] do
it { expect(described_class.graphql_name).to eq('Package') }
it 'includes all the package fields' do it 'includes all the package fields' do
expected_fields = %w[ expected_fields = %w[
id name version created_at updated_at package_type tags project pipelines versions id name version package_type
created_at updated_at
project
tags pipelines versions
] ]
expect(described_class).to include_graphql_fields(*expected_fields) expect(described_class).to include_graphql_fields(*expected_fields)
......
...@@ -2,20 +2,10 @@ ...@@ -2,20 +2,10 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageComposerDetails'] do RSpec.describe GitlabSchema.types['PackageWithoutVersions'] do
it { expect(described_class.graphql_name).to eq('PackageComposerDetails') }
it 'includes all the package fields' do it 'includes all the package fields' do
expected_fields = %w[ expected_fields = %w[
id name version created_at updated_at package_type tags project pipelines versions id name version created_at updated_at package_type tags project pipelines
]
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) expect(described_class).to include_graphql_fields(*expected_fields)
......
...@@ -95,9 +95,9 @@ RSpec.describe GitlabSchema.types['Query'] do ...@@ -95,9 +95,9 @@ RSpec.describe GitlabSchema.types['Query'] do
it { is_expected.to have_graphql_type(Types::ContainerRepositoryDetailsType) } it { is_expected.to have_graphql_type(Types::ContainerRepositoryDetailsType) }
end end
describe 'package_composer_details field' do describe 'package field' do
subject { described_class.fields['packageComposerDetails'] } subject { described_class.fields['package'] }
it { is_expected.to have_graphql_type(Types::Packages::Composer::DetailsType) } it { is_expected.to have_graphql_type(Types::Packages::PackageType) }
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
RSpec.describe 'package composer details' do RSpec.describe 'package details' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:package) { create(:composer_package, project: project) } let_it_be(:package) { create(:composer_package, project: project) }
let_it_be(:composer_json) { { name: 'name', type: 'type', license: 'license', version: 1 } }
let_it_be(:composer_metadatum) do 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 # 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 # 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 }) create(:composer_metadatum, package: package, target_sha: 'foo_sha', composer_json: composer_json)
end end
let(:depth) { 3 }
let(:excluded) { ['metadata'] }
let(:query) do let(:query) do
graphql_query_for( graphql_query_for(:package, { id: package_global_id }, <<~FIELDS)
'packageComposerDetails', #{all_graphql_fields_for('Package', max_depth: depth, excluded: excluded)}
{ id: package_global_id }, metadata {
all_graphql_fields_for('PackageComposerDetails', max_depth: 2) #{query_graphql_fragment('ComposerMetadata')}
) }
FIELDS
end end
let(:user) { project.owner } let(:user) { project.owner }
let(:package_global_id) { package.to_global_id.to_s } let(:package_global_id) { global_id_of(package) }
let(:package_composer_details_response) { graphql_data.dig('packageComposerDetails') } let(:package_details) { graphql_data_at(:package) }
subject { post_graphql(query, current_user: user) } subject { post_graphql(query, current_user: user) }
...@@ -33,7 +38,43 @@ RSpec.describe 'package composer details' do ...@@ -33,7 +38,43 @@ RSpec.describe 'package composer details' do
end end
it 'matches the JSON schema' do it 'matches the JSON schema' do
expect(package_composer_details_response).to match_schema('graphql/packages/package_composer_details') expect(package_details).to match_schema('graphql/packages/package_details')
end
it 'includes the fields of the correct package' do
expect(package_details).to include(
'id' => package_global_id,
'metadata' => {
'targetSha' => 'foo_sha',
'composerJson' => composer_json.transform_keys(&:to_s).transform_values(&:to_s)
}
)
end
end
context 'there are other versions of this package' do
let(:depth) { 3 }
let(:excluded) { %w[metadata project tags pipelines] } # to limit the query complexity
let_it_be(:siblings) { create_list(:composer_package, 2, project: project, name: package.name) }
it 'includes the sibling versions' do
subject
expect(graphql_data_at(:package, :versions, :nodes)).to match_array(
siblings.map { |p| a_hash_including('id' => global_id_of(p)) }
)
end
context 'going deeper' do
let(:depth) { 6 }
it 'does not create a cycle of versions' do
subject
expect(graphql_data_at(:package, :versions, :nodes, :version)).to be_present
expect(graphql_data_at(:package, :versions, :nodes, :versions)).not_to be_present
end
end end
end end
end end
...@@ -5,16 +5,27 @@ require 'spec_helper' ...@@ -5,16 +5,27 @@ require 'spec_helper'
RSpec.describe 'getting a package list for a project' do RSpec.describe 'getting a package list for a project' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:package) { create(:package, project: project) } let_it_be(:package) { create(:package, project: project) }
let(:packages_data) { graphql_data['project']['packages']['edges'] } let_it_be(:maven_package) { create(:maven_package, project: project) }
let_it_be(:debian_package) { create(:debian_package, project: project) }
let_it_be(:composer_package) { create(:composer_package, project: project) }
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(:project, :packages, :edges, :node, :name) }
let(:fields) do let(:fields) do
<<~QUERY <<~QUERY
edges { edges {
node { node {
#{all_graphql_fields_for('packages'.classify)} #{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
metadata { #{query_graphql_fragment('ComposerMetadata')} }
} }
} }
QUERY QUERY
...@@ -37,7 +48,17 @@ RSpec.describe 'getting a package list for a project' do ...@@ -37,7 +48,17 @@ RSpec.describe 'getting a package list for a project' do
it_behaves_like 'a working graphql query' it_behaves_like 'a working graphql query'
it 'returns packages successfully' do it 'returns packages successfully' do
expect(packages_data[0]['node']['name']).to eq package.name expect(package_names).to contain_exactly(
package.name,
maven_package.name,
debian_package.name,
composer_package.name
)
end
it 'deals with metadata' do
target_shas = graphql_data_at(:project, :packages, :edges, :node, :metadata, :target_sha)
expect(target_shas).to contain_exactly(composer_metadatum.target_sha)
end end
end end
...@@ -53,7 +74,7 @@ RSpec.describe 'getting a package list for a project' do ...@@ -53,7 +74,7 @@ RSpec.describe 'getting a package list for a project' do
end end
end end
context 'when the user is not autenthicated' do context 'when the user is not authenticated' do
before do before do
post_graphql(query) post_graphql(query)
end end
......
...@@ -47,7 +47,7 @@ module Graphql ...@@ -47,7 +47,7 @@ module Graphql
NO_SKIP = ->(_name, _field) { false } NO_SKIP = ->(_name, _field) { false }
def self.select_fields(type, skip = NO_SKIP, parent_types = Set.new, max_depth = 3) def self.select_fields(type, skip = NO_SKIP, parent_types = Set.new, max_depth = 3)
return new if max_depth <= 0 return new if max_depth <= 0 || !type.kind.fields?
new(type.fields.flat_map do |name, field| new(type.fields.flat_map do |name, field|
next [] if skip[name, field] next [] if skip[name, field]
......
...@@ -237,6 +237,10 @@ module GraphqlHelpers ...@@ -237,6 +237,10 @@ module GraphqlHelpers
query_graphql_path([[name, args], node_selection], fields) query_graphql_path([[name, args], node_selection], fields)
end end
def query_graphql_fragment(name)
"... on #{name} { #{all_graphql_fields_for(name)} }"
end
# e.g: # e.g:
# query_graphql_path(%i[foo bar baz], all_graphql_fields_for('Baz')) # query_graphql_path(%i[foo bar baz], all_graphql_fields_for('Baz'))
# => foo { bar { baz { x y z } } } # => foo { bar { baz { x y z } } }
......
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