Commit 4c098afb authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents aa136207 d9a2c221
...@@ -3,13 +3,15 @@ ...@@ -3,13 +3,15 @@
module Packages module Packages
module Maven module Maven
class PackageFinder class PackageFinder
attr_reader :path, :current_user, :project, :group include ::Packages::FinderHelper
include Gitlab::Utils::StrongMemoize
def initialize(path, current_user, project: nil, group: nil) def initialize(path, current_user, project: nil, group: nil, order_by_package_file: false)
@path = path @path = path
@current_user = current_user @current_user = current_user
@project = project @project = project
@group = group @group = group
@order_by_package_file = order_by_package_file
end end
def execute def execute
...@@ -23,9 +25,9 @@ module Packages ...@@ -23,9 +25,9 @@ module Packages
private private
def base def base
if project if @project
packages_for_a_single_project packages_for_a_single_project
elsif group elsif @group
packages_for_multiple_projects packages_for_multiple_projects
else else
::Packages::Package.none ::Packages::Package.none
...@@ -33,8 +35,13 @@ module Packages ...@@ -33,8 +35,13 @@ module Packages
end end
def packages_with_path def packages_with_path
matching_packages = base.only_maven_packages_with_path(path, use_cte: group.present?) matching_packages = base.only_maven_packages_with_path(@path, use_cte: @group.present?)
matching_packages = matching_packages.order_by_package_file if versionless_package?(matching_packages)
if group_level_improvements?
matching_packages = matching_packages.order_by_package_file if @order_by_package_file
else
matching_packages = matching_packages.order_by_package_file if versionless_package?(matching_packages)
end
matching_packages matching_packages
end end
...@@ -48,19 +55,29 @@ module Packages ...@@ -48,19 +55,29 @@ module Packages
# Produces a query that retrieves packages from a single project. # Produces a query that retrieves packages from a single project.
def packages_for_a_single_project def packages_for_a_single_project
project.packages @project.packages
end end
# Produces a query that retrieves packages from multiple projects that # Produces a query that retrieves packages from multiple projects that
# the current user can view within a group. # the current user can view within a group.
def packages_for_multiple_projects def packages_for_multiple_projects
::Packages::Package.for_projects(projects_visible_to_current_user) if group_level_improvements?
packages_visible_to_user(@current_user, within_group: @group)
else
::Packages::Package.for_projects(projects_visible_to_current_user)
end
end end
# Returns the projects that the current user can view within a group. # Returns the projects that the current user can view within a group.
def projects_visible_to_current_user def projects_visible_to_current_user
group.all_projects @group.all_projects
.public_or_visible_to_user(current_user) .public_or_visible_to_user(@current_user)
end
def group_level_improvements?
strong_memoize(:group_level_improvements) do
Feature.enabled?(:maven_packages_group_level_improvements)
end
end end
end end
end end
......
...@@ -136,7 +136,9 @@ class Packages::Package < ApplicationRecord ...@@ -136,7 +136,9 @@ class Packages::Package < ApplicationRecord
after_commit :update_composer_cache, on: :destroy, if: -> { composer? } after_commit :update_composer_cache, on: :destroy, if: -> { composer? }
def self.for_projects(projects) def self.for_projects(projects)
return none unless projects.any? unless Feature.enabled?(:maven_packages_group_level_improvements)
return none unless projects.any?
end
where(project_id: projects) where(project_id: projects)
end end
......
...@@ -1851,10 +1851,12 @@ class User < ApplicationRecord ...@@ -1851,10 +1851,12 @@ class User < ApplicationRecord
end end
def dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil) def dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil)
callouts = self.callouts.with_feature_name(feature_name) callout = callouts_by_feature_name[feature_name]
callouts = callouts.with_dismissed_after(ignore_dismissal_earlier_than) if ignore_dismissal_earlier_than
callouts.any? return false unless callout
return callout.dismissed_after?(ignore_dismissal_earlier_than) if ignore_dismissal_earlier_than
true
end end
# Load the current highest access by looking directly at the user's memberships # Load the current highest access by looking directly at the user's memberships
...@@ -1917,6 +1919,10 @@ class User < ApplicationRecord ...@@ -1917,6 +1919,10 @@ class User < ApplicationRecord
private private
def callouts_by_feature_name
@callouts_by_feature_name ||= callouts.index_by(&:feature_name)
end
def authorized_groups_without_shared_membership def authorized_groups_without_shared_membership
Group.from_union([ Group.from_union([
groups, groups,
......
...@@ -38,6 +38,7 @@ class UserCallout < ApplicationRecord ...@@ -38,6 +38,7 @@ class UserCallout < ApplicationRecord
uniqueness: { scope: :user_id }, uniqueness: { scope: :user_id },
inclusion: { in: UserCallout.feature_names.keys } inclusion: { in: UserCallout.feature_names.keys }
scope :with_feature_name, -> (feature_name) { where(feature_name: UserCallout.feature_names[feature_name]) } def dismissed_after?(dismissed_after)
scope :with_dismissed_after, -> (dismissed_after) { where('dismissed_at > ?', dismissed_after) } dismissed_at > dismissed_after
end
end end
...@@ -126,7 +126,7 @@ ...@@ -126,7 +126,7 @@
.card .card
.card-header .card-header
= html_escape(_("%{group_name} group members")) % { group_name: "<strong>#{html_escape(@group.name)}</strong>".html_safe } = html_escape(_("%{group_name} group members")) % { group_name: "<strong>#{html_escape(@group.name)}</strong>".html_safe }
%span.badge.badge-pill= @group.members.size %span.badge.badge-pill= @group.users_count
.float-right .float-right
= link_to group_group_members_path(@group), class: 'btn btn-default gl-button btn-sm' do = link_to group_group_members_path(@group), class: 'btn btn-default gl-button btn-sm' do
= sprite_icon('pencil-square', css_class: 'gl-icon') = sprite_icon('pencil-square', css_class: 'gl-icon')
......
---
title: Make VALIDATION_REQUEST_TIMEOUT configurable
merge_request: 57521
author:
type: changed
---
title: Preload all user callouts in a single request
merge_request: 57679
author:
type: performance
---
name: maven_packages_group_level_improvements
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57600
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326099
milestone: '13.11'
type: development
group: group::package
default_enabled: false
...@@ -27,7 +27,15 @@ Response Code Legend: ...@@ -27,7 +27,15 @@ Response Code Legend:
## Configuration ## Configuration
Set the `EXTERNAL_VALIDATION_SERVICE_URL` to the external service URL and enable `ci_external_validation_service` feature flag. To configure external pipeline validation:
1. Set the `EXTERNAL_VALIDATION_SERVICE_URL` environment variable to the external
service URL.
1. Enable the `ci_external_validation_service` feature flag.
By default, requests to the external service time out after five seconds. To override
the default, set the `EXTERNAL_VALIDATION_SERVICE_TIMEOUT` environment variable to the
required number of seconds.
## Payload Schema ## Payload Schema
......
...@@ -431,7 +431,7 @@ module EE ...@@ -431,7 +431,7 @@ module EE
override :users_count override :users_count
def users_count def users_count
return all_group_members.count unless minimal_access_role_allowed? return all_group_members.count if minimal_access_role_allowed?
members.count members.count
end end
......
---
title: Count minimal access users in admin area group page
merge_request: 57630
author:
type: fixed
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require "spec_helper" require "spec_helper"
RSpec.describe EE::Ci::RunnersHelper do RSpec.describe EE::Ci::RunnersHelper do
let_it_be(:user) { create(:user) } let_it_be(:user, refind: true) { create(:user) }
let_it_be(:namespace) { create(:namespace, owner: user) } let_it_be(:namespace) { create(:namespace, owner: user) }
let_it_be(:project) { create(:project, namespace: namespace) } let_it_be(:project) { create(:project, namespace: namespace) }
......
...@@ -765,6 +765,35 @@ RSpec.describe Group do ...@@ -765,6 +765,35 @@ RSpec.describe Group do
end end
end end
describe '#users_count' do
subject { group.users_count }
let(:group) { create(:group) }
let(:user) { create(:user) }
context 'with `minimal_access_role` not licensed' do
before do
stub_licensed_features(minimal_access_role: false)
create(:group_member, :minimal_access, user: user, source: group)
end
it 'does not count the minimal access user' do
expect(group.users_count).to eq(0)
end
end
context 'with `minimal_access_role` licensed' do
before do
stub_licensed_features(minimal_access_role: true)
create(:group_member, :minimal_access, user: user, source: group)
end
it 'counts the minimal access user' do
expect(group.users_count).to eq(1)
end
end
end
describe '#saml_discovery_token' do describe '#saml_discovery_token' do
it 'returns existing tokens' do it 'returns existing tokens' do
group = create(:group, saml_discovery_token: 'existing') group = create(:group, saml_discovery_token: 'existing')
......
...@@ -279,7 +279,7 @@ RSpec.describe Security::StoreReportService, '#execute' do ...@@ -279,7 +279,7 @@ RSpec.describe Security::StoreReportService, '#execute' do
end end
context 'vulnerability issue link' do context 'vulnerability issue link' do
context 'when there is no assoiciated issue feedback with finding' do context 'when there is no associated issue feedback with finding' do
it 'does not insert issue links from the new pipeline' do it 'does not insert issue links from the new pipeline' do
expect { subject }.to change { Vulnerabilities::IssueLink.count }.by(0) expect { subject }.to change { Vulnerabilities::IssueLink.count }.by(0)
end end
......
...@@ -77,6 +77,22 @@ module API ...@@ -77,6 +77,22 @@ module API
request.head? && request.head? &&
file.fog_credentials[:provider] == 'AWS' file.fog_credentials[:provider] == 'AWS'
end end
def fetch_package(file_name:, project: nil, group: nil)
order_by_package_file = false
if Feature.enabled?(:maven_packages_group_level_improvements)
order_by_package_file = file_name.include?(::Packages::Maven::Metadata.filename) &&
!params[:path].include?(::Packages::Maven::FindOrCreatePackageService::SNAPSHOT_TERM)
end
::Packages::Maven::PackageFinder.new(
params[:path],
current_user,
project: project,
group: group,
order_by_package_file: order_by_package_file
).execute!
end
end end
desc 'Download the maven package file at instance level' do desc 'Download the maven package file at instance level' do
...@@ -97,8 +113,7 @@ module API ...@@ -97,8 +113,7 @@ module API
authorize_read_package!(project) authorize_read_package!(project)
package = ::Packages::Maven::PackageFinder package = fetch_package(file_name: file_name, project: project)
.new(params[:path], current_user, project: project).execute!
package_file = ::Packages::PackageFileFinder package_file = ::Packages::PackageFileFinder
.new(package, file_name).execute! .new(package, file_name).execute!
...@@ -133,8 +148,7 @@ module API ...@@ -133,8 +148,7 @@ module API
not_found!('Group') unless can?(current_user, :read_group, group) not_found!('Group') unless can?(current_user, :read_group, group)
package = ::Packages::Maven::PackageFinder package = fetch_package(file_name: file_name, group: group)
.new(params[:path], current_user, group: group).execute!
authorize_read_package!(package.project) authorize_read_package!(package.project)
...@@ -171,8 +185,7 @@ module API ...@@ -171,8 +185,7 @@ module API
file_name, format = extract_format(params[:file_name]) file_name, format = extract_format(params[:file_name])
package = ::Packages::Maven::PackageFinder package = fetch_package(file_name: file_name, project: user_project)
.new(params[:path], current_user, project: user_project).execute!
package_file = ::Packages::PackageFileFinder package_file = ::Packages::PackageFileFinder
.new(package, file_name).execute! .new(package, file_name).execute!
......
...@@ -10,7 +10,7 @@ module Gitlab ...@@ -10,7 +10,7 @@ module Gitlab
InvalidResponseCode = Class.new(StandardError) InvalidResponseCode = Class.new(StandardError)
VALIDATION_REQUEST_TIMEOUT = 5 DEFAULT_VALIDATION_REQUEST_TIMEOUT = 5
ACCEPTED_STATUS = 200 ACCEPTED_STATUS = 200
DOT_COM_REJECTED_STATUS = 406 DOT_COM_REJECTED_STATUS = 406
GENERAL_REJECTED_STATUS = (400..499).freeze GENERAL_REJECTED_STATUS = (400..499).freeze
...@@ -70,11 +70,18 @@ module Gitlab ...@@ -70,11 +70,18 @@ module Gitlab
def validate_service_request def validate_service_request
Gitlab::HTTP.post( Gitlab::HTTP.post(
validation_service_url, timeout: VALIDATION_REQUEST_TIMEOUT, validation_service_url, timeout: validation_service_timeout,
body: validation_service_payload.to_json body: validation_service_payload.to_json
) )
end end
def validation_service_timeout
timeout = ENV['EXTERNAL_VALIDATION_SERVICE_TIMEOUT'].to_i
return timeout if timeout > 0
DEFAULT_VALIDATION_REQUEST_TIMEOUT
end
def validation_service_url def validation_service_url
ENV['EXTERNAL_VALIDATION_SERVICE_URL'] ENV['EXTERNAL_VALIDATION_SERVICE_URL']
end end
......
...@@ -11,7 +11,8 @@ RSpec.describe ::Packages::Maven::PackageFinder do ...@@ -11,7 +11,8 @@ RSpec.describe ::Packages::Maven::PackageFinder do
let(:param_path) { nil } let(:param_path) { nil }
let(:param_project) { nil } let(:param_project) { nil }
let(:param_group) { nil } let(:param_group) { nil }
let(:finder) { described_class.new(param_path, user, project: param_project, group: param_group) } let(:param_order_by_package_file) { false }
let(:finder) { described_class.new(param_path, user, project: param_project, group: param_group, order_by_package_file: param_order_by_package_file) }
before do before do
group.add_developer(user) group.add_developer(user)
...@@ -46,7 +47,23 @@ RSpec.describe ::Packages::Maven::PackageFinder do ...@@ -46,7 +47,23 @@ RSpec.describe ::Packages::Maven::PackageFinder do
context 'within a group' do context 'within a group' do
let(:param_group) { group } let(:param_group) { group }
it_behaves_like 'handling valid and invalid paths' context 'with maven_packages_group_level_improvements enabled' do
before do
stub_feature_flags(maven_packages_group_level_improvements: true)
expect(finder).to receive(:packages_visible_to_user).with(user, within_group: group).and_call_original
end
it_behaves_like 'handling valid and invalid paths'
end
context 'with maven_packages_group_level_improvements disabled' do
before do
stub_feature_flags(maven_packages_group_level_improvements: false)
expect(finder).not_to receive(:packages_visible_to_user)
end
it_behaves_like 'handling valid and invalid paths'
end
end end
context 'across all projects' do context 'across all projects' do
...@@ -76,7 +93,39 @@ RSpec.describe ::Packages::Maven::PackageFinder do ...@@ -76,7 +93,39 @@ RSpec.describe ::Packages::Maven::PackageFinder do
create(:package_file, :xml, package: package2) create(:package_file, :xml, package: package2)
end end
it { is_expected.to eq(package2) } context 'with maven_packages_group_level_improvements enabled' do
before do
stub_feature_flags(maven_packages_group_level_improvements: true)
expect(finder).not_to receive(:versionless_package?)
end
context 'without order by package file' do
it { is_expected.to eq(package3) }
end
context 'with order by package file' do
let(:param_order_by_package_file) { true }
it { is_expected.to eq(package2) }
end
end
context 'with maven_packages_group_level_improvements disabled' do
before do
stub_feature_flags(maven_packages_group_level_improvements: false)
expect(finder).to receive(:versionless_package?).and_call_original
end
context 'without order by package file' do
it { is_expected.to eq(package2) }
end
context 'with order by package file' do
let(:param_order_by_package_file) { true }
it { is_expected.to eq(package2) }
end
end
end end
end end
end end
......
...@@ -62,11 +62,42 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do ...@@ -62,11 +62,42 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
it 'respects the defined payload schema' do it 'respects the defined payload schema' do
expect(::Gitlab::HTTP).to receive(:post) do |_url, params| expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
expect(params[:body]).to match_schema('/external_validation') expect(params[:body]).to match_schema('/external_validation')
expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT)
end end
perform! perform!
end end
context 'with EXTERNAL_VALIDATION_SERVICE_TIMEOUT defined' do
before do
stub_env('EXTERNAL_VALIDATION_SERVICE_TIMEOUT', validation_service_timeout)
end
context 'with valid value' do
let(:validation_service_timeout) { '1' }
it 'uses defined timeout' do
expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
expect(params[:timeout]).to eq(1)
end
perform!
end
end
context 'with invalid value' do
let(:validation_service_timeout) { '??' }
it 'uses default timeout' do
expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT)
end
perform!
end
end
end
shared_examples 'successful external authorization' do shared_examples 'successful external authorization' do
it 'does not drop the pipeline' do it 'does not drop the pipeline' do
perform! perform!
......
...@@ -99,6 +99,34 @@ RSpec.describe Packages::Package, type: :model do ...@@ -99,6 +99,34 @@ RSpec.describe Packages::Package, type: :model do
end end
end end
describe '.for_projects' do
let_it_be(:package1) { create(:maven_package) }
let_it_be(:package2) { create(:maven_package) }
let_it_be(:package3) { create(:maven_package) }
let(:projects) { ::Project.id_in([package1.project_id, package2.project_id]) }
subject { described_class.for_projects(projects.select(:id)) }
it 'returns package1 and package2' do
expect(projects).not_to receive(:any?)
expect(subject).to match_array([package1, package2])
end
context 'with maven_packages_group_level_improvements disabled' do
before do
stub_feature_flags(maven_packages_group_level_improvements: false)
end
it 'returns package1 and package2' do
expect(projects).to receive(:any?).and_call_original
expect(subject).to match_array([package1, package2])
end
end
end
describe 'validations' do describe 'validations' do
subject { build(:package) } subject { build(:package) }
......
...@@ -18,36 +18,14 @@ RSpec.describe UserCallout do ...@@ -18,36 +18,14 @@ RSpec.describe UserCallout do
it { is_expected.to validate_uniqueness_of(:feature_name).scoped_to(:user_id).ignoring_case_sensitivity } it { is_expected.to validate_uniqueness_of(:feature_name).scoped_to(:user_id).ignoring_case_sensitivity }
end end
describe 'scopes' do describe '#dismissed_after?' do
describe '.with_feature_name' do let(:some_feature_name) { described_class.feature_names.keys.second }
let(:second_feature_name) { described_class.feature_names.keys.second } let(:callout_dismissed_month_ago) { create(:user_callout, feature_name: some_feature_name, dismissed_at: 1.month.ago )}
let(:last_feature_name) { described_class.feature_names.keys.last } let(:callout_dismissed_day_ago) { create(:user_callout, feature_name: some_feature_name, dismissed_at: 1.day.ago )}
it 'returns callout for requested feature name only' do it 'returns whether a callout dismissed after specified date' do
callout1 = create(:user_callout, feature_name: second_feature_name ) expect(callout_dismissed_month_ago.dismissed_after?(15.days.ago)).to eq(false)
create(:user_callout, feature_name: last_feature_name ) expect(callout_dismissed_day_ago.dismissed_after?(15.days.ago)).to eq(true)
callouts = described_class.with_feature_name(second_feature_name)
expect(callouts).to match_array([callout1])
end
end
describe '.with_dismissed_after' do
let(:some_feature_name) { described_class.feature_names.keys.second }
let(:callout_dismissed_month_ago) { create(:user_callout, feature_name: some_feature_name, dismissed_at: 1.month.ago )}
it 'does not return callouts dismissed before specified date' do
callouts = described_class.with_dismissed_after(15.days.ago)
expect(callouts).to match_array([])
end
it 'returns callouts dismissed after specified date' do
callouts = described_class.with_dismissed_after(2.months.ago)
expect(callouts).to match_array([callout_dismissed_month_ago])
end
end end
end end
end end
This diff is collapsed.
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