Commit ac764452 authored by Stan Hu's avatar Stan Hu

Merge branch '347409-migrate-package-details-page-to-vue-router-2' into 'master'

Graphql: Add package managers api paths to details type

See merge request gitlab-org/gitlab!77518
parents a4f56897 d0093862
......@@ -3,6 +3,8 @@
module Types
module Packages
class PackageDetailsType < PackageType
include ::PackagesHelper
graphql_name 'PackageDetailsType'
description 'Represents a package details in the Package Registry. Note that this type is in beta and susceptible to changes'
authorize :read_package
......@@ -21,6 +23,15 @@ module Types
description: 'Pipelines that built the package.',
deprecated: { reason: 'Due to scalability concerns, this field is going to be removed', milestone: '14.6' }
field :composer_config_repository_url, GraphQL::Types::String, null: true, description: 'Url of the Composer setup endpoint.'
field :composer_url, GraphQL::Types::String, null: true, description: 'Url of the Composer endpoint.'
field :conan_url, GraphQL::Types::String, null: true, description: 'Url of the Conan project endpoint.'
field :maven_url, GraphQL::Types::String, null: true, description: 'Url of the Maven project endpoint.'
field :npm_url, GraphQL::Types::String, null: true, description: 'Url of the NPM project endpoint.'
field :nuget_url, GraphQL::Types::String, null: true, description: 'Url of the Nuget project endpoint.'
field :pypi_setup_url, GraphQL::Types::String, null: true, description: 'Url of the PyPi project setup endpoint.'
field :pypi_url, GraphQL::Types::String, null: true, description: 'Url of the PyPi project endpoint.'
def versions
object.versions
end
......@@ -32,6 +43,38 @@ module Types
object.package_files
end
end
def composer_config_repository_url
composer_config_repository_name(object.project.group&.id)
end
def composer_url
composer_registry_url(object.project.group&.id)
end
def conan_url
package_registry_project_url(object.project.id, :conan)
end
def maven_url
package_registry_project_url(object.project.id, :maven)
end
def npm_url
package_registry_project_url(object.project.id, :npm)
end
def nuget_url
nuget_package_registry_url(object.project.id)
end
def pypi_setup_url
package_registry_project_url(object.project.id, :pypi)
end
def pypi_url
pypi_registry_url(object.project.id)
end
end
end
end
# frozen_string_literal: true
module PackagesHelper
include ::API::Helpers::RelatedResourcesHelpers
def package_sort_path(options = {})
"#{request.path}?#{options.to_param}"
end
......
......@@ -12701,15 +12701,23 @@ Represents a package details in the Package Registry. Note that this type is in
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="packagedetailstypecandestroy"></a>`canDestroy` | [`Boolean!`](#boolean) | Whether the user can destroy the package. |
| <a id="packagedetailstypecomposerconfigrepositoryurl"></a>`composerConfigRepositoryUrl` | [`String`](#string) | Url of the Composer setup endpoint. |
| <a id="packagedetailstypecomposerurl"></a>`composerUrl` | [`String`](#string) | Url of the Composer endpoint. |
| <a id="packagedetailstypeconanurl"></a>`conanUrl` | [`String`](#string) | Url of the Conan project endpoint. |
| <a id="packagedetailstypecreatedat"></a>`createdAt` | [`Time!`](#time) | Date of creation. |
| <a id="packagedetailstypedependencylinks"></a>`dependencyLinks` | [`PackageDependencyLinkConnection`](#packagedependencylinkconnection) | Dependency link. (see [Connections](#connections)) |
| <a id="packagedetailstypeid"></a>`id` | [`PackagesPackageID!`](#packagespackageid) | ID of the package. |
| <a id="packagedetailstypemavenurl"></a>`mavenUrl` | [`String`](#string) | Url of the Maven project endpoint. |
| <a id="packagedetailstypemetadata"></a>`metadata` | [`PackageMetadata`](#packagemetadata) | Package metadata. |
| <a id="packagedetailstypename"></a>`name` | [`String!`](#string) | Name of the package. |
| <a id="packagedetailstypenpmurl"></a>`npmUrl` | [`String`](#string) | Url of the NPM project endpoint. |
| <a id="packagedetailstypenugeturl"></a>`nugetUrl` | [`String`](#string) | Url of the Nuget project endpoint. |
| <a id="packagedetailstypepackagefiles"></a>`packageFiles` | [`PackageFileConnection`](#packagefileconnection) | Package files. (see [Connections](#connections)) |
| <a id="packagedetailstypepackagetype"></a>`packageType` | [`PackageTypeEnum!`](#packagetypeenum) | Package type. |
| <a id="packagedetailstypepipelines"></a>`pipelines` **{warning-solid}** | [`PipelineConnection`](#pipelineconnection) | **Deprecated** in 14.6. Due to scalability concerns, this field is going to be removed. |
| <a id="packagedetailstypeproject"></a>`project` | [`Project!`](#project) | Project where the package is stored. |
| <a id="packagedetailstypepypisetupurl"></a>`pypiSetupUrl` | [`String`](#string) | Url of the PyPi project setup endpoint. |
| <a id="packagedetailstypepypiurl"></a>`pypiUrl` | [`String`](#string) | Url of the PyPi project endpoint. |
| <a id="packagedetailstypestatus"></a>`status` | [`PackageStatus!`](#packagestatus) | Package status. |
| <a id="packagedetailstypetags"></a>`tags` | [`PackageTagConnection`](#packagetagconnection) | Package tags. (see [Connections](#connections)) |
| <a id="packagedetailstypeupdatedat"></a>`updatedAt` | [`Time!`](#time) | Date of most recent update. |
......@@ -149,6 +149,30 @@
}
}
}
},
"npmUrl": {
"type": "string"
},
"mavenUrl": {
"type": "string"
},
"conanUrl": {
"type": "string"
},
"nugetUrl": {
"type": "string"
},
"pypiUrl": {
"type": "string"
},
"pypiSetupUrl": {
"type": "string"
},
"composerUrl": {
"type": "string"
},
"composerConfigRepositoryUrl": {
"type": "string"
}
}
}
......@@ -5,7 +5,10 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageDetailsType'] do
it 'includes all the package fields' do
expected_fields = %w[
id name version created_at updated_at package_type tags project pipelines versions package_files dependency_links
id name version created_at updated_at package_type tags project
pipelines versions package_files dependency_links
npm_url maven_url conan_url nuget_url pypi_url pypi_setup_url
composer_url composer_config_repository_url
]
expect(described_class).to include_graphql_fields(*expected_fields)
......
......@@ -4,7 +4,9 @@ require 'spec_helper'
RSpec.describe 'package details' do
include GraphqlHelpers
let_it_be_with_reload(:project) { create(:project) }
let_it_be_with_reload(:group) { create(:group) }
let_it_be_with_reload(:project) { create(:project, group: group) }
let_it_be(:user) { create(:user) }
let_it_be(:composer_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
......@@ -17,7 +19,6 @@ RSpec.describe 'package details' do
let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] }
let(:metadata) { query_graphql_fragment('ComposerMetadata') }
let(:package_files) {all_graphql_fields_for('PackageFile')}
let(:user) { project.owner }
let(:package_global_id) { global_id_of(composer_package) }
let(:package_details) { graphql_data_at(:package) }
......@@ -37,170 +38,198 @@ RSpec.describe 'package details' do
subject { post_graphql(query, current_user: user) }
it_behaves_like 'a working graphql query' do
context 'with unauthorized user' do
before do
subject
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
it 'matches the JSON schema' do
expect(package_details).to match_schema('graphql/packages/package_details')
it 'returns no packages' do
subject
expect(graphql_data_at(:package)).to be_nil
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: composer_package.name) }
context 'with authorized user' do
before do
project.add_developer(user)
end
it 'includes the sibling versions' do
subject
it_behaves_like 'a working graphql query' do
before do
subject
end
expect(graphql_data_at(:package, :versions, :nodes)).to match_array(
siblings.map { |p| a_hash_including('id' => global_id_of(p)) }
)
it 'matches the JSON schema' do
expect(package_details).to match_schema('graphql/packages/package_details')
end
end
context 'going deeper' do
let(:depth) { 6 }
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: composer_package.name) }
it 'does not create a cycle of versions' do
it 'includes the sibling versions' do
subject
expect(graphql_data_at(:package, :versions, :nodes, :version)).to be_present
expect(graphql_data_at(:package, :versions, :nodes, :versions, :nodes)).to eq [nil, nil]
expect(graphql_data_at(:package, :versions, :nodes)).to match_array(
siblings.map { |p| a_hash_including('id' => global_id_of(p)) }
)
end
end
end
context 'with package files pending destruction' do
let_it_be(:package_file) { create(:package_file, package: composer_package) }
let_it_be(:package_file_pending_destruction) { create(:package_file, :pending_destruction, package: composer_package) }
context 'going deeper' do
let(:depth) { 6 }
let(:package_file_ids) { graphql_data_at(:package, :package_files, :nodes).map { |node| node["id"] } }
it 'does not create a cycle of versions' do
subject
it 'does not return them' do
subject
expect(package_file_ids).to contain_exactly(package_file.to_global_id.to_s)
expect(graphql_data_at(:package, :versions, :nodes, :version)).to be_present
expect(graphql_data_at(:package, :versions, :nodes, :versions, :nodes)).to match_array [nil, nil]
end
end
end
context 'with packages_installable_package_files disabled' do
before do
stub_feature_flags(packages_installable_package_files: false)
end
context 'with package files pending destruction' do
let_it_be(:package_file) { create(:package_file, package: composer_package) }
let_it_be(:package_file_pending_destruction) { create(:package_file, :pending_destruction, package: composer_package) }
it 'returns them' do
let(:package_file_ids) { graphql_data_at(:package, :package_files, :nodes).map { |node| node["id"] } }
it 'does not return them' do
subject
expect(package_file_ids).to contain_exactly(package_file_pending_destruction.to_global_id.to_s, package_file.to_global_id.to_s)
expect(package_file_ids).to contain_exactly(package_file.to_global_id.to_s)
end
end
end
context 'with a batched query' do
let_it_be(:conan_package) { create(:conan_package, project: project) }
context 'with packages_installable_package_files disabled' do
before do
stub_feature_flags(packages_installable_package_files: false)
end
let(:batch_query) do
<<~QUERY
{
a: package(id: "#{global_id_of(composer_package)}") { name }
b: package(id: "#{global_id_of(conan_package)}") { name }
}
QUERY
it 'returns them' do
subject
expect(package_file_ids).to contain_exactly(package_file_pending_destruction.to_global_id.to_s, package_file.to_global_id.to_s)
end
end
end
let(:a_packages_names) { graphql_data_at(:a, :packages, :nodes, :name) }
context 'with a batched query' do
let_it_be(:conan_package) { create(:conan_package, project: project) }
it 'returns an error for the second package and data for the first' do
post_graphql(batch_query, current_user: user)
let(:batch_query) do
<<~QUERY
{
a: package(id: "#{global_id_of(composer_package)}") { name }
b: package(id: "#{global_id_of(conan_package)}") { name }
}
QUERY
end
expect(graphql_data_at(:a, :name)).to eq(composer_package.name)
let(:a_packages_names) { graphql_data_at(:a, :packages, :nodes, :name) }
expect_graphql_errors_to_include [/Package details can be requested only for one package at a time/]
expect(graphql_data_at(:b)).to be(nil)
end
end
it 'returns an error for the second package and data for the first' do
post_graphql(batch_query, current_user: user)
context 'with unauthorized user' do
let_it_be(:user) { create(:user) }
expect(graphql_data_at(:a, :name)).to eq(composer_package.name)
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
expect_graphql_errors_to_include [/Package details can be requested only for one package at a time/]
expect(graphql_data_at(:b)).to be(nil)
end
end
it 'returns no packages' do
subject
context 'pipelines field', :aggregate_failures do
let(:pipelines) { create_list(:ci_pipeline, 6, project: project) }
let(:pipeline_gids) { pipelines.sort_by(&:id).map(&:to_gid).map(&:to_s).reverse }
expect(graphql_data_at(:package)).to be_nil
end
end
before do
composer_package.pipelines = pipelines
composer_package.save!
end
context 'pipelines field', :aggregate_failures do
let(:pipelines) { create_list(:ci_pipeline, 6, project: project) }
let(:pipeline_gids) { pipelines.sort_by(&:id).map(&:to_gid).map(&:to_s).reverse }
def run_query(args)
pipelines_nodes = <<~QUERY
nodes {
id
}
pageInfo {
startCursor
endCursor
}
QUERY
query = graphql_query_for(:package, { id: package_global_id }, query_graphql_field("pipelines", args, pipelines_nodes))
post_graphql(query, current_user: user)
end
before do
composer_package.pipelines = pipelines
composer_package.save!
end
it 'loads the second page with pagination first correctly' do
run_query(first: 2)
pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
def run_query(args)
pipelines_nodes = <<~QUERY
nodes {
id
}
pageInfo {
startCursor
endCursor
}
QUERY
expect(pipeline_ids).to eq(pipeline_gids[0..1])
query = graphql_query_for(:package, { id: package_global_id }, query_graphql_field("pipelines", args, pipelines_nodes))
post_graphql(query, current_user: user)
end
cursor = graphql_data.dig('package', 'pipelines', 'pageInfo', 'endCursor')
run_query(first: 2, after: cursor)
it 'loads the second page with pagination first correctly' do
run_query(first: 2)
pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
expect(pipeline_ids).to eq(pipeline_gids[0..1])
expect(pipeline_ids).to eq(pipeline_gids[2..3])
end
cursor = graphql_data.dig('package', 'pipelines', 'pageInfo', 'endCursor')
it 'loads the second page with pagination last correctly' do
run_query(last: 2)
pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
run_query(first: 2, after: cursor)
expect(pipeline_ids).to eq(pipeline_gids[4..5])
pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
cursor = graphql_data.dig('package', 'pipelines', 'pageInfo', 'startCursor')
expect(pipeline_ids).to eq(pipeline_gids[2..3])
end
run_query(last: 2, before: cursor)
it 'loads the second page with pagination last correctly' do
run_query(last: 2)
pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
expect(pipeline_ids).to eq(pipeline_gids[4..5])
expect(pipeline_ids).to eq(pipeline_gids[2..3])
end
end
cursor = graphql_data.dig('package', 'pipelines', 'pageInfo', 'startCursor')
context 'package managers paths' do
before do
subject
end
run_query(last: 2, before: cursor)
it 'returns npm_url correctly' do
expect(graphql_data_at(:package, :npm_url)).to eq("http://localhost/api/v4/projects/#{project.id}/packages/npm")
end
pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
it 'returns maven_url correctly' do
expect(graphql_data_at(:package, :maven_url)).to eq("http://localhost/api/v4/projects/#{project.id}/packages/maven")
end
expect(pipeline_ids).to eq(pipeline_gids[2..3])
end
it 'returns conan_url correctly' do
expect(graphql_data_at(:package, :conan_url)).to eq("http://localhost/api/v4/projects/#{project.id}/packages/conan")
end
context 'with unauthorized user' do
let_it_be(:user) { create(:user) }
it 'returns nuget_url correctly' do
expect(graphql_data_at(:package, :nuget_url)).to eq("http://localhost/api/v4/projects/#{project.id}/packages/nuget/index.json")
end
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
it 'returns pypi_url correctly' do
expect(graphql_data_at(:package, :pypi_url)).to eq("http://__token__:<your_personal_token>@localhost/api/v4/projects/#{project.id}/packages/pypi/simple")
end
it 'returns no packages' do
run_query(first: 2)
it 'returns pypi_setup_url correctly' do
expect(graphql_data_at(:package, :pypi_setup_url)).to eq("http://localhost/api/v4/projects/#{project.id}/packages/pypi")
end
it 'returns composer_url correctly' do
expect(graphql_data_at(:package, :composer_url)).to eq("http://localhost/api/v4/group/#{group.id}/-/packages/composer/packages.json")
end
expect(graphql_data_at(:package)).to be_nil
it 'returns composer_config_repository_url correctly' do
expect(graphql_data_at(:package, :composer_config_repository_url)).to eq("localhost/#{group.id}")
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