Commit 384849e5 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '211926-support-nuget-dependencies-in-api-presenters' into 'master'

Support nuget dependencies in the API

See merge request gitlab-org/gitlab!33389
parents ff8f5459 65ffed50
...@@ -15,4 +15,5 @@ class Packages::DependencyLink < ApplicationRecord ...@@ -15,4 +15,5 @@ class Packages::DependencyLink < ApplicationRecord
scope :includes_dependency, -> { includes(:dependency) } scope :includes_dependency, -> { includes(:dependency) }
scope :for_package, ->(package) { where(package_id: package.id) } scope :for_package, ->(package) { where(package_id: package.id) }
scope :preload_dependency, -> { preload(:dependency) } scope :preload_dependency, -> { preload(:dependency) }
scope :preload_nuget_metadatum, -> { preload(:nuget_metadatum) }
end end
...@@ -6,7 +6,8 @@ module Packages ...@@ -6,7 +6,8 @@ module Packages
include ::API::Helpers::RelatedResourcesHelpers include ::API::Helpers::RelatedResourcesHelpers
BLANK_STRING = '' BLANK_STRING = ''
EMPTY_ARRAY = [].freeze PACKAGE_DEPENDENCY_GROUP = 'PackageDependencyGroup'
PACKAGE_DEPENDENCY = 'PackageDependency'
private private
...@@ -42,7 +43,7 @@ module Packages ...@@ -42,7 +43,7 @@ module Packages
{ {
json_url: json_url_for(package), json_url: json_url_for(package),
authors: BLANK_STRING, authors: BLANK_STRING,
dependencies: EMPTY_ARRAY, dependency_groups: dependency_groups_for(package),
package_name: package.name, package_name: package.name,
package_version: package.version, package_version: package.version,
archive_url: archive_url_for(package), archive_url: archive_url_for(package),
...@@ -52,6 +53,46 @@ module Packages ...@@ -52,6 +53,46 @@ module Packages
} }
end end
def dependency_groups_for(package)
base_nuget_id = "#{json_url_for(package)}#dependencyGroup"
dependency_links_grouped_by_target_framework(package).map do |target_framework, dependency_links|
nuget_id = target_framework_nuget_id(base_nuget_id, target_framework)
{
id: nuget_id,
type: PACKAGE_DEPENDENCY_GROUP,
target_framework: target_framework,
dependencies: dependencies_for(nuget_id, dependency_links)
}.compact
end
end
def dependency_links_grouped_by_target_framework(package)
package
.dependency_links
.includes_dependency
.preload_nuget_metadatum
.group_by { |dependency_link| dependency_link.nuget_metadatum&.target_framework }
end
def dependencies_for(nuget_id, dependency_links)
return [] if dependency_links.empty?
dependency_links.map do |dependency_link|
dependency = dependency_link.dependency
{
id: "#{nuget_id}/#{dependency.name.downcase}",
type: PACKAGE_DEPENDENCY,
name: dependency.name,
range: dependency.version_pattern
}
end
end
def target_framework_nuget_id(base_nuget_id, target_framework)
target_framework.blank? ? base_nuget_id : "#{base_nuget_id}/#{target_framework.downcase}"
end
def metadatum_for(package) def metadatum_for(package)
metadatum = package.nuget_metadatum metadatum = package.nuget_metadatum
return {} unless metadatum return {} unless metadatum
......
---
title: Support nuget dependencies in the API
merge_request: 33389
author:
type: fixed
# frozen_string_literal: true
module EE
module API
module Entities
module Nuget
class Dependency < Grape::Entity
expose :id, as: :@id
expose :type, as: :@type
expose :name, as: :id
expose :range
end
end
end
end
end
# frozen_string_literal: true
module EE
module API
module Entities
module Nuget
class DependencyGroup < Grape::Entity
expose :id, as: :@id
expose :type, as: :@type
expose :target_framework, as: :targetFramework, expose_nil: false
expose :dependencies, using: EE::API::Entities::Nuget::Dependency
end
end
end
end
end
...@@ -7,7 +7,7 @@ module EE ...@@ -7,7 +7,7 @@ module EE
class PackageMetadataCatalogEntry < Grape::Entity class PackageMetadataCatalogEntry < Grape::Entity
expose :json_url, as: :@id expose :json_url, as: :@id
expose :authors expose :authors
expose :dependencies, as: :dependencyGroups expose :dependency_groups, as: :dependencyGroups, using: EE::API::Entities::Nuget::DependencyGroup
expose :package_name, as: :id expose :package_name, as: :id
expose :package_version, as: :version expose :package_version, as: :version
expose :tags expose :tags
......
{
"type": "object",
"required": ["@id", "@type", "dependencies"],
"properties": {
"@id": { "type": "string" },
"@type": { "const": "PackageDependencyGroup" },
"targetFramework": { "type": "string" },
"dependencies": {
"type": "array",
"items": {
"type": "object",
"required": ["@id", "@type", "id", "range"],
"properties": {
"@id": { "type": "string" },
"@type": { "const": "PackageDependency" },
"id": { "type": "string" },
"range": { "type": "string" }
}
}
}
}
}
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
"properties": { "properties": {
"@id": { "type": "string" }, "@id": { "type": "string" },
"authors": { "const": "" }, "authors": { "const": "" },
"dependencyGroups": { "const": [] },
"id": { "type": "string" }, "id": { "type": "string" },
"packageContent": { "type": "string" }, "packageContent": { "type": "string" },
"summary": { "const": "" }, "summary": { "const": "" },
...@@ -18,7 +17,11 @@ ...@@ -18,7 +17,11 @@
"projectUrl": { "type": "string" }, "projectUrl": { "type": "string" },
"licenseUrl": { "type": "string" }, "licenseUrl": { "type": "string" },
"iconUrl": { "type": "string" }, "iconUrl": { "type": "string" },
"version": { "type": "string" } "version": { "type": "string" },
"dependencyGroups": {
"type": "array",
"items": { "$ref": "./dependency_group.json" }
}
} }
} }
} }
......
...@@ -26,7 +26,6 @@ ...@@ -26,7 +26,6 @@
"properties": { "properties": {
"@id": { "type": "string" }, "@id": { "type": "string" },
"authors": { "const": "" }, "authors": { "const": "" },
"dependencyGroups": { "const": [] },
"id": { "type": "string" }, "id": { "type": "string" },
"packageContent": { "type": "string" }, "packageContent": { "type": "string" },
"summary": { "const": "" }, "summary": { "const": "" },
...@@ -34,7 +33,11 @@ ...@@ -34,7 +33,11 @@
"projectUrl": { "type": "string" }, "projectUrl": { "type": "string" },
"licenseUrl": { "type": "string" }, "licenseUrl": { "type": "string" },
"iconUrl": { "type": "string" }, "iconUrl": { "type": "string" },
"version": { "type": "string" } "version": { "type": "string" },
"dependencyGroups": {
"type": "array",
"items": { "$ref": "./dependency_group.json" }
}
} }
} }
} }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EE::API::Entities::Nuget::DependencyGroup do
let(:dependency_group) do
{
id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependencygroup',
type: 'PackageDependencyGroup',
target_framework: 'fwk test',
dependencies: [
{
id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
type: 'PackageDependency',
name: 'Dependency',
range: '2.0.0'
}
]
}
end
let(:expected) do
{
'@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependencygroup',
'@type': 'PackageDependencyGroup',
'targetFramework': 'fwk test',
'dependencies': [
{
'@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
'@type': 'PackageDependency',
'id': 'Dependency',
'range': '2.0.0'
}
]
}
end
let(:entity) { described_class.new(dependency_group) }
subject { entity.as_json }
it { is_expected.to eq(expected) }
context 'dependency group without target framework' do
let(:dependency_group_with_no_target_framework) { dependency_group.tap { |dg| dg[:target_framework] = nil } }
let(:expected_no_target_framework) { expected.except(:targetFramework) }
let(:entity) { described_class.new(dependency_group_with_no_target_framework) }
it { is_expected.to eq(expected_no_target_framework) }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EE::API::Entities::Nuget::Dependency do
let(:dependency) do
{
id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
type: 'PackageDependency',
name: 'Dependency',
range: '2.0.0'
}
end
let(:expected) do
{
'@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
'@type': 'PackageDependency',
'id': 'Dependency',
'range': '2.0.0'
}
end
let(:entity) { described_class.new(dependency) }
subject { entity.as_json }
it { is_expected.to eq(expected) }
end
...@@ -7,7 +7,7 @@ RSpec.describe EE::API::Entities::Nuget::PackageMetadataCatalogEntry do ...@@ -7,7 +7,7 @@ RSpec.describe EE::API::Entities::Nuget::PackageMetadataCatalogEntry do
{ {
json_url: 'http://sandbox.com/json/package', json_url: 'http://sandbox.com/json/package',
authors: 'Authors', authors: 'Authors',
dependencies: [], dependency_groups: [],
package_name: 'PackageTest', package_name: 'PackageTest',
package_version: '1.2.3', package_version: '1.2.3',
tags: 'tag1 tag2 tag3', tags: 'tag1 tag2 tag3',
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Packages::Nuget::PackageMetadataPresenter do RSpec.describe Packages::Nuget::PackageMetadataPresenter do
include_context 'with expected presenters dependency groups'
let_it_be(:package) { create(:nuget_package, :with_metadatum) } let_it_be(:package) { create(:nuget_package, :with_metadatum) }
let_it_be(:tag1) { create(:packages_tag, name: 'tag1', package: package) } let_it_be(:tag1) { create(:packages_tag, name: 'tag1', package: package) }
let_it_be(:tag2) { create(:packages_tag, name: 'tag2', package: package) } let_it_be(:tag2) { create(:packages_tag, name: 'tag2', package: package) }
...@@ -27,13 +29,17 @@ RSpec.describe Packages::Nuget::PackageMetadataPresenter do ...@@ -27,13 +29,17 @@ RSpec.describe Packages::Nuget::PackageMetadataPresenter do
describe '#catalog_entry' do describe '#catalog_entry' do
subject { presenter.catalog_entry } subject { presenter.catalog_entry }
before do
create_dependencies_for(package)
end
it 'returns an entry structure' do it 'returns an entry structure' do
entry = subject entry = subject
expect(entry).to be_a Hash expect(entry).to be_a Hash
%i[json_url archive_url].each { |field| expect(entry[field]).not_to be_blank } %i[json_url archive_url].each { |field| expect(entry[field]).not_to be_blank }
%i[authors summary].each { |field| expect(entry[field]).to be_blank } %i[authors summary].each { |field| expect(entry[field]).to be_blank }
expect(entry[:dependencies]).to eq [] expect(entry[:dependency_groups]).to eq expected_dependency_groups(package.project_id, package.name, package.version)
expect(entry[:package_name]).to eq package.name expect(entry[:package_name]).to eq package.name
expect(entry[:package_version]).to eq package.version expect(entry[:package_version]).to eq package.version
expect(entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2') expect(entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2')
......
...@@ -3,7 +3,10 @@ ...@@ -3,7 +3,10 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Packages::Nuget::PackagesMetadataPresenter do RSpec.describe Packages::Nuget::PackagesMetadataPresenter do
let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: 'Dummy.Package') } include_context 'with expected presenters dependency groups'
let_it_be(:project) { create(:project) }
let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: 'Dummy.Package', project: project) }
let_it_be(:presenter) { described_class.new(packages) } let_it_be(:presenter) { described_class.new(packages) }
describe '#count' do describe '#count' do
...@@ -20,6 +23,8 @@ RSpec.describe Packages::Nuget::PackagesMetadataPresenter do ...@@ -20,6 +23,8 @@ RSpec.describe Packages::Nuget::PackagesMetadataPresenter do
before do before do
packages.each do |pkg| packages.each do |pkg|
tag_names.each { |tag| create(:packages_tag, package: pkg, name: tag) } tag_names.each { |tag| create(:packages_tag, package: pkg, name: tag) }
create_dependencies_for(pkg)
end end
end end
...@@ -49,7 +54,7 @@ RSpec.describe Packages::Nuget::PackagesMetadataPresenter do ...@@ -49,7 +54,7 @@ RSpec.describe Packages::Nuget::PackagesMetadataPresenter do
catalog_entry = pkg[:catalog_entry] catalog_entry = pkg[:catalog_entry]
%i[json_url archive_url package_name package_version].each { |field| expect(catalog_entry[field]).not_to be_blank } %i[json_url archive_url package_name package_version].each { |field| expect(catalog_entry[field]).not_to be_blank }
%i[authors summary].each { |field| expect(catalog_entry[field]).to be_blank } %i[authors summary].each { |field| expect(catalog_entry[field]).to be_blank }
expect(catalog_entry[:dependencies]).to eq [] expect(catalog_entry[:dependency_groups]).to eq(expected_dependency_groups(project.id, catalog_entry[:package_name], catalog_entry[:package_version]))
expect(catalog_entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2') expect(catalog_entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2')
%i[project_url license_url icon_url].each do |field| %i[project_url license_url icon_url].each do |field|
......
...@@ -203,6 +203,8 @@ RSpec.describe API::NugetPackages do ...@@ -203,6 +203,8 @@ RSpec.describe API::NugetPackages do
end end
describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do
include_context 'with expected presenters dependency groups'
let_it_be(:package_name) { 'Dummy.Package' } let_it_be(:package_name) { 'Dummy.Package' }
let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) } let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) }
let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } } let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } }
...@@ -210,6 +212,10 @@ RSpec.describe API::NugetPackages do ...@@ -210,6 +212,10 @@ RSpec.describe API::NugetPackages do
subject { get api(url) } subject { get api(url) }
before do
packages.each { |pkg| create_dependencies_for(pkg) }
end
context 'with packages features enabled' do context 'with packages features enabled' do
before do before do
stub_licensed_features(packages: true) stub_licensed_features(packages: true)
...@@ -264,6 +270,8 @@ RSpec.describe API::NugetPackages do ...@@ -264,6 +270,8 @@ RSpec.describe API::NugetPackages do
end end
describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do
include_context 'with expected presenters dependency groups'
let_it_be(:package_name) { 'Dummy.Package' } let_it_be(:package_name) { 'Dummy.Package' }
let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) } let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) }
let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') } let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') }
...@@ -271,6 +279,10 @@ RSpec.describe API::NugetPackages do ...@@ -271,6 +279,10 @@ RSpec.describe API::NugetPackages do
subject { get api(url) } subject { get api(url) }
before do
create_dependencies_for(package)
end
context 'with packages features enabled' do context 'with packages features enabled' do
before do before do
stub_licensed_features(packages: true) stub_licensed_features(packages: true)
......
# frozen_string_literal: true
RSpec.shared_context 'with expected presenters dependency groups' do
def expected_dependency_groups(project_id, package_name, package_version)
[
{
id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/.netstandard2.0",
target_framework: '.NETStandard2.0',
type: 'PackageDependencyGroup',
dependencies: [
{
id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/.netstandard2.0/newtonsoft.json",
range: '12.0.3',
name: 'Newtonsoft.Json',
type: 'PackageDependency'
}
]
},
{
id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup",
type: 'PackageDependencyGroup',
dependencies: [
{
id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/castle.core",
range: '4.4.1',
name: 'Castle.Core',
type: 'PackageDependency'
}
]
}
]
end
def create_dependencies_for(package)
dependency1 = Packages::Dependency.find_by(name: 'Newtonsoft.Json', version_pattern: '12.0.3') || create(:packages_dependency, name: 'Newtonsoft.Json', version_pattern: '12.0.3')
dependency2 = Packages::Dependency.find_by(name: 'Castle.Core', version_pattern: '4.4.1') || create(:packages_dependency, name: 'Castle.Core', version_pattern: '4.4.1')
create(:packages_dependency_link, :with_nuget_metadatum, package: package, dependency: dependency1)
create(:packages_dependency_link, package: package, dependency: dependency2)
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