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
module Resolvers
# No return types defined because they can be different.
# rubocop: disable Graphql/ResolverType
class PackageDetailsResolver < BaseResolver
type ::Types::Packages::PackageType, null: true
argument :id, ::Types::GlobalIDType[::Packages::Package],
required: true,
description: 'The global ID of the package.'
......
......@@ -2,7 +2,7 @@
module Resolvers
class PackagesResolver < BaseResolver
type Types::Packages::PackageType, null: true
type Types::Packages::PackageType.connection_type, null: true
def resolve(**args)
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
module Packages
module Composer
class MetadatumType < BaseObject
graphql_name 'PackageComposerMetadatumType'
description 'Composer metadatum'
graphql_name 'ComposerMetadata'
description 'Composer metadata'
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 @@
module Types
module Packages
class PackageType < BaseObject
class PackageType < PackageWithoutVersionsType
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
field :versions, ::Types::Packages::PackageWithoutVersionsType.connection_type, null: true,
description: 'The other versions of the package.'
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
description: 'A single issue of the project',
resolver: Resolvers::IssuesResolver.single
field :packages, Types::Packages::PackageType.connection_type, null: true,
field :packages,
description: 'Packages of the project',
resolver: Resolvers::PackagesResolver
......
......@@ -58,9 +58,8 @@ 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',
field :package,
description: 'Find a package',
resolver: Resolvers::PackageDetailsResolver
field :user, Types::UserType,
......
......@@ -94,7 +94,6 @@ module Security
def gitlab_ci_yml_attributes
@gitlab_ci_yml_attributes ||= begin
config_content = @project.repository.blob_data_at(@project.repository.root_ref_sha, ci_config_file)
return {} unless 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.
"""
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
"""
......@@ -16880,142 +16895,27 @@ Represents a package in the Package Registry
"""
type Package {
"""
The created date.
Date of creation.
"""
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
}
"""
Details of a Composer package
"""
type PackageComposerDetails {
"""
The Composer metadatum.
"""
composerMetadatum: PackageComposerMetadatumType!
"""
The created date.
ID of the package.
"""
createdAt: Time!
id: PackagesPackageID!
"""
The ID of the package.
Package metadata.
"""
id: ID!
metadata: PackageMetadata
"""
The name of the package.
Name of the package.
"""
name: String!
"""
The type of the package.
Package type.
"""
packageType: PackageTypeEnum!
......@@ -17050,7 +16950,7 @@ type PackageComposerDetails {
project: Project!
"""
The package tags.
Package tags.
"""
tags(
"""
......@@ -17075,12 +16975,12 @@ type PackageComposerDetails {
): PackageTagConnection
"""
The updated date.
Date of most recent update.
"""
updatedAt: Time!
"""
The version of the package.
Version string.
"""
version: String
......@@ -17107,7 +17007,7 @@ type PackageComposerDetails {
Returns the last _n_ elements from the list.
"""
last: Int
): PackageConnection
): PackageWithoutVersionsConnection
}
"""
......@@ -17135,21 +17035,6 @@ type PackageComposerJsonType {
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.
"""
......@@ -17265,6 +17150,11 @@ type PackageFileRegistryEdge {
node: PackageFileRegistry
}
"""
Represents metadata associated with a Package
"""
union PackageMetadata = ComposerMetadata
"""
Namespace-level Package Registry settings
"""
......@@ -17388,6 +17278,136 @@ enum PackageTypeEnum {
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.
"""
......@@ -20538,14 +20558,14 @@ type Query {
): Namespace
"""
Find a composer package
Find a package
"""
packageComposerDetails(
package(
"""
The global ID of the package.
"""
id: PackagesPackageID!
): PackageComposerDetails
): Package
"""
Find a project
......
......@@ -593,6 +593,15 @@ Represents a ComplianceFramework associated with a Project.
| `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`. |
### ComposerMetadata
Composer metadata.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `composerJson` | PackageComposerJsonType! | Data of the Composer JSON file. |
| `targetSha` | String! | Target SHA of the package. |
### ConfigureSastPayload
Autogenerated return type of ConfigureSast.
......@@ -2541,34 +2550,17 @@ 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. |
| `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. |
| `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 | The package tags. |
| `updatedAt` | Time! | The updated date. |
| `version` | String | The version of the package. |
| `versions` | PackageConnection | The other versions of the package. |
| `tags` | PackageTagConnection | Package tags. |
| `updatedAt` | Time! | Date of most recent update. |
| `version` | String | Version string. |
| `versions` | PackageWithoutVersionsConnection | The other versions of the package. |
### PackageComposerJsonType
......@@ -2581,15 +2573,6 @@ Represents a 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
Represents the Geo sync and verification state of a package file.
......@@ -2625,6 +2608,23 @@ Represents a package tag.
| `name` | String! | The name of the tag. |
| `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
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",
"additionalProperties": false,
"required": [
"id", "name", "createdAt", "updatedAt", "version", "packageType",
"project", "tags", "pipelines", "versions", "metadata"
],
"properties": {
"id": {
"type": "string"
......@@ -16,21 +21,46 @@
"version": {
"type": ["string", "null"]
},
"package_type": {
"packageType": {
"type": ["string"],
"enum": ["MAVEN", "NPM", "CONAN", "NUGET", "PYPI", "COMPOSER", "GENERIC", "GOLANG", "DEBIAN"]
},
"tags": {
"type": "object"
"type": "object",
"additionalProperties": false,
"properties": {
"pageInfo": { "type": "object" },
"edges": { "type": "array" },
"nodes": { "type": "array" }
}
},
"project": {
"type": "object"
},
"pipelines": {
"type": "object"
"type": "object",
"additionalProperties": false,
"properties": {
"pageInfo": { "type": "object" },
"count": { "type": "integer" },
"edges": { "type": "array" },
"nodes": { "type": "array" }
}
},
"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'
RSpec.describe Resolvers::PackageDetailsResolver do
include GraphqlHelpers
include ::Gitlab::Graphql::Laziness
let_it_be_with_reload(:project) { create(:project) }
let_it_be(:user) { project.owner }
......@@ -11,10 +12,10 @@ RSpec.describe Resolvers::PackageDetailsResolver do
describe '#resolve' do
let(:args) do
{ id: package.to_global_id.to_s }
{ id: global_id_of(package) }
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) }
end
......
......@@ -2,9 +2,7 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageComposerMetadatumType'] do
it { expect(described_class.graphql_name).to eq('PackageComposerMetadatumType') }
RSpec.describe GitlabSchema.types['ComposerMetadata'] do
it 'includes composer metadatum fields' do
expected_fields = %w[
target_sha composer_json
......
......@@ -3,11 +3,12 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['Package'] do
it { expect(described_class.graphql_name).to eq('Package') }
it 'includes all the package fields' do
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)
......
......@@ -2,20 +2,10 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageComposerDetails'] do
it { expect(described_class.graphql_name).to eq('PackageComposerDetails') }
RSpec.describe GitlabSchema.types['PackageWithoutVersions'] do
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
id name version created_at updated_at package_type tags project pipelines
]
expect(described_class).to include_graphql_fields(*expected_fields)
......
......@@ -95,9 +95,9 @@ 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'] }
describe 'package field' do
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
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'package composer details' do
RSpec.describe 'package 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_json) { { name: 'name', type: 'type', license: 'license', version: 1 } }
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 })
create(:composer_metadatum, package: package, target_sha: 'foo_sha', composer_json: composer_json)
end
let(:depth) { 3 }
let(:excluded) { ['metadata'] }
let(:query) do
graphql_query_for(
'packageComposerDetails',
{ id: package_global_id },
all_graphql_fields_for('PackageComposerDetails', max_depth: 2)
)
graphql_query_for(:package, { id: package_global_id }, <<~FIELDS)
#{all_graphql_fields_for('Package', max_depth: depth, excluded: excluded)}
metadata {
#{query_graphql_fragment('ComposerMetadata')}
}
FIELDS
end
let(:user) { project.owner }
let(:package_global_id) { package.to_global_id.to_s }
let(:package_composer_details_response) { graphql_data.dig('packageComposerDetails') }
let(:package_global_id) { global_id_of(package) }
let(:package_details) { graphql_data_at(:package) }
subject { post_graphql(query, current_user: user) }
......@@ -33,7 +38,43 @@ RSpec.describe 'package composer details' do
end
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
......@@ -5,16 +5,27 @@ require 'spec_helper'
RSpec.describe 'getting a package list for a project' do
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(: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
<<~QUERY
edges {
node {
#{all_graphql_fields_for('packages'.classify)}
#{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
metadata { #{query_graphql_fragment('ComposerMetadata')} }
}
}
QUERY
......@@ -37,7 +48,17 @@ RSpec.describe 'getting a package list for a project' do
it_behaves_like 'a working graphql query'
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
......@@ -53,7 +74,7 @@ RSpec.describe 'getting a package list for a project' do
end
end
context 'when the user is not autenthicated' do
context 'when the user is not authenticated' do
before do
post_graphql(query)
end
......
......@@ -47,7 +47,7 @@ module Graphql
NO_SKIP = ->(_name, _field) { false }
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|
next [] if skip[name, field]
......
......@@ -237,6 +237,10 @@ module GraphqlHelpers
query_graphql_path([[name, args], node_selection], fields)
end
def query_graphql_fragment(name)
"... on #{name} { #{all_graphql_fields_for(name)} }"
end
# e.g:
# query_graphql_path(%i[foo bar baz], all_graphql_fields_for('Baz'))
# => 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