Commit f7594f8f authored by Steve Abrams's avatar Steve Abrams

Merge branch '339835-tweak-npm-package-finder' into 'master'

NPM package registry: fine tune a query for the instance level

See merge request gitlab-org/gitlab!69572
parents 6721c424 53060a18
...@@ -5,17 +5,23 @@ module Packages ...@@ -5,17 +5,23 @@ module Packages
delegate :find_by_version, to: :execute delegate :find_by_version, to: :execute
delegate :last, to: :execute delegate :last, to: :execute
def initialize(package_name, project: nil, namespace: nil) # /!\ CAUTION: don't use last_of_each_version: false with find_by_version. Ordering is not
# guaranteed!
def initialize(package_name, project: nil, namespace: nil, last_of_each_version: true)
@package_name = package_name @package_name = package_name
@project = project @project = project
@namespace = namespace @namespace = namespace
@last_of_each_version = last_of_each_version
end end
def execute def execute
base.npm result = base.npm
.with_name(@package_name) .with_name(@package_name)
.installable .installable
.last_of_each_version
return result unless @last_of_each_version
result.last_of_each_version
end end
private private
......
---
name: npm_finder_query_avoid_duplicated_conditions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69572
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340099
milestone: '14.3'
type: development
group: group::package
default_enabled: false
...@@ -57,7 +57,11 @@ module API ...@@ -57,7 +57,11 @@ module API
.by_path(namespace_path) .by_path(namespace_path)
next unless namespace next unless namespace
finder = ::Packages::Npm::PackageFinder.new(package_name, namespace: namespace) finder = ::Packages::Npm::PackageFinder.new(
package_name,
namespace: namespace,
last_of_each_version: Feature.disabled?(:npm_finder_query_avoid_duplicated_conditions)
)
finder.last&.project_id finder.last&.project_id
end end
......
...@@ -7,6 +7,7 @@ RSpec.describe ::Packages::Npm::PackageFinder do ...@@ -7,6 +7,7 @@ RSpec.describe ::Packages::Npm::PackageFinder do
let(:project) { package.project } let(:project) { package.project }
let(:package_name) { package.name } let(:package_name) { package.name }
let(:last_of_each_version) { true }
shared_examples 'accepting a namespace for' do |example_name| shared_examples 'accepting a namespace for' do |example_name|
before do before do
...@@ -38,6 +39,8 @@ RSpec.describe ::Packages::Npm::PackageFinder do ...@@ -38,6 +39,8 @@ RSpec.describe ::Packages::Npm::PackageFinder do
end end
describe '#execute' do describe '#execute' do
subject { finder.execute }
shared_examples 'finding packages by name' do shared_examples 'finding packages by name' do
it { is_expected.to eq([package]) } it { is_expected.to eq([package]) }
...@@ -56,13 +59,27 @@ RSpec.describe ::Packages::Npm::PackageFinder do ...@@ -56,13 +59,27 @@ RSpec.describe ::Packages::Npm::PackageFinder do
end end
end end
subject { finder.execute } shared_examples 'handling last_of_each_version' do
include_context 'last_of_each_version setup context'
context 'disabled' do
let(:last_of_each_version) { false }
it { is_expected.to contain_exactly(package1, package2) }
end
context 'enabled' do
it { is_expected.to contain_exactly(package2) }
end
end
context 'with a project' do context 'with a project' do
let(:finder) { described_class.new(package_name, project: project) } let(:finder) { described_class.new(package_name, project: project, last_of_each_version: last_of_each_version) }
it_behaves_like 'finding packages by name' it_behaves_like 'finding packages by name'
it_behaves_like 'handling last_of_each_version'
context 'set to nil' do context 'set to nil' do
let(:project) { nil } let(:project) { nil }
...@@ -71,10 +88,12 @@ RSpec.describe ::Packages::Npm::PackageFinder do ...@@ -71,10 +88,12 @@ RSpec.describe ::Packages::Npm::PackageFinder do
end end
context 'with a namespace' do context 'with a namespace' do
let(:finder) { described_class.new(package_name, namespace: namespace) } let(:finder) { described_class.new(package_name, namespace: namespace, last_of_each_version: last_of_each_version) }
it_behaves_like 'accepting a namespace for', 'finding packages by name' it_behaves_like 'accepting a namespace for', 'finding packages by name'
it_behaves_like 'accepting a namespace for', 'handling last_of_each_version'
context 'set to nil' do context 'set to nil' do
let_it_be(:namespace) { nil } let_it_be(:namespace) { nil }
...@@ -98,16 +117,28 @@ RSpec.describe ::Packages::Npm::PackageFinder do ...@@ -98,16 +117,28 @@ RSpec.describe ::Packages::Npm::PackageFinder do
end end
end end
shared_examples 'handling last_of_each_version' do
include_context 'last_of_each_version setup context'
context 'enabled' do
it { is_expected.to eq(package2) }
end
end
context 'with a project' do context 'with a project' do
let(:finder) { described_class.new(package_name, project: project) } let(:finder) { described_class.new(package_name, project: project, last_of_each_version: last_of_each_version) }
it_behaves_like 'finding packages by version' it_behaves_like 'finding packages by version'
it_behaves_like 'handling last_of_each_version'
end end
context 'with a namespace' do context 'with a namespace' do
let(:finder) { described_class.new(package_name, namespace: namespace) } let(:finder) { described_class.new(package_name, namespace: namespace, last_of_each_version: last_of_each_version) }
it_behaves_like 'accepting a namespace for', 'finding packages by version' it_behaves_like 'accepting a namespace for', 'finding packages by version'
it_behaves_like 'accepting a namespace for', 'handling last_of_each_version'
end end
end end
...@@ -118,11 +149,26 @@ RSpec.describe ::Packages::Npm::PackageFinder do ...@@ -118,11 +149,26 @@ RSpec.describe ::Packages::Npm::PackageFinder do
it { is_expected.to eq(package) } it { is_expected.to eq(package) }
end end
shared_examples 'handling project or namespace parameter' do shared_examples 'handling last_of_each_version' do
include_context 'last_of_each_version setup context'
context 'disabled' do
let(:last_of_each_version) { false }
it { is_expected.to eq(package2) }
end
context 'enabled' do
it { is_expected.to eq(package2) }
end
end
context 'with a project' do context 'with a project' do
let(:finder) { described_class.new(package_name, project: project) } let(:finder) { described_class.new(package_name, project: project, last_of_each_version: last_of_each_version) }
it_behaves_like 'finding package by last' it_behaves_like 'finding package by last'
it_behaves_like 'handling last_of_each_version'
end end
context 'with a namespace' do context 'with a namespace' do
...@@ -130,6 +176,8 @@ RSpec.describe ::Packages::Npm::PackageFinder do ...@@ -130,6 +176,8 @@ RSpec.describe ::Packages::Npm::PackageFinder do
it_behaves_like 'accepting a namespace for', 'finding package by last' it_behaves_like 'accepting a namespace for', 'finding package by last'
it_behaves_like 'accepting a namespace for', 'handling last_of_each_version'
context 'with duplicate packages' do context 'with duplicate packages' do
let_it_be(:namespace) { create(:group) } let_it_be(:namespace) { create(:group) }
let_it_be(:subgroup1) { create(:group, parent: namespace) } let_it_be(:subgroup1) { create(:group, parent: namespace) }
...@@ -146,7 +194,4 @@ RSpec.describe ::Packages::Npm::PackageFinder do ...@@ -146,7 +194,4 @@ RSpec.describe ::Packages::Npm::PackageFinder do
end end
end end
end end
it_behaves_like 'handling project or namespace parameter'
end
end end
...@@ -10,6 +10,7 @@ RSpec.describe API::NpmInstancePackages do ...@@ -10,6 +10,7 @@ RSpec.describe API::NpmInstancePackages do
include_context 'npm api setup' include_context 'npm api setup'
shared_examples 'handling all endpoints' do
describe 'GET /api/v4/packages/npm/*package_name' do describe 'GET /api/v4/packages/npm/*package_name' do
it_behaves_like 'handling get metadata requests', scope: :instance do it_behaves_like 'handling get metadata requests', scope: :instance do
let(:url) { api("/packages/npm/#{package_name}") } let(:url) { api("/packages/npm/#{package_name}") }
...@@ -33,4 +34,15 @@ RSpec.describe API::NpmInstancePackages do ...@@ -33,4 +34,15 @@ RSpec.describe API::NpmInstancePackages do
let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") } let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
end end
end end
end
it_behaves_like 'handling all endpoints'
context 'with npm_finder_query_avoid_duplicated_conditions disabled' do
before do
stub_feature_flags(npm_finder_query_avoid_duplicated_conditions: false)
end
it_behaves_like 'handling all endpoints'
end
end end
# frozen_string_literal: true
RSpec.shared_context 'last_of_each_version setup context' do
let_it_be(:package1) { create(:npm_package, name: 'test', version: '1.2.3', project: project) }
let_it_be(:package2) { create(:npm_package, name: 'test2', version: '1.2.3', project: project) }
let(:package_name) { 'test' }
let(:version) { '1.2.3' }
before do
# create a duplicated package without triggering model validation errors
package2.update_column(:name, 'test')
end
end
...@@ -8,7 +8,8 @@ RSpec.shared_context 'npm api setup' do ...@@ -8,7 +8,8 @@ RSpec.shared_context 'npm api setup' do
let_it_be(:group) { create(:group, name: 'test-group') } let_it_be(:group) { create(:group, name: 'test-group') }
let_it_be(:namespace) { group } let_it_be(:namespace) { group }
let_it_be(:project, reload: true) { create(:project, :public, namespace: namespace) } let_it_be(:project, reload: true) { create(:project, :public, namespace: namespace) }
let_it_be(:package, reload: true) { create(:npm_package, project: project, name: "@#{group.path}/scoped_package") } let_it_be(:package1, reload: true) { create(:npm_package, project: project, name: "@#{group.path}/scoped_package", version: '1.2.4') }
let_it_be(:package, reload: true) { create(:npm_package, project: project, name: "@#{group.path}/scoped_package", version: '1.2.3') }
let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) } let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running, project: project) } let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running, project: project) }
...@@ -16,6 +17,11 @@ RSpec.shared_context 'npm api setup' do ...@@ -16,6 +17,11 @@ RSpec.shared_context 'npm api setup' do
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
let(:package_name) { package.name } let(:package_name) { package.name }
before do
# create a duplicated package without triggering model validation errors
package1.update_column(:version, '1.2.3')
end
end end
RSpec.shared_context 'set package name from package name type' do RSpec.shared_context 'set package name from package name type' do
......
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