Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-04-14 03:18:32 +00:00
parent 270353e1ff
commit 361def3666
14 changed files with 485 additions and 22 deletions

View File

@ -49,9 +49,9 @@ Which tier is this feature available in?
Please add links to the relevant merge requests.
- As soon as possible, but no later than the third milestone preceding the major release (for example, given the following release schedule: `14.8, 14.9, 14.10, 15.0` `14.8` is the third milestone preceding the major release):
- [ ] A [deprecation announcement entry](https://about.gitlab.com/handbook/marketing/blog/release-posts/#creating-a-deprecation-announcement) has been created so the deprecation will appear in release posts and on the [general deprecation page](https://docs.gitlab.com/ee/update/deprecations).
- [ ] A [deprecation announcement entry](https://about.gitlab.com/handbook/marketing/blog/release-posts/#creating-the-announcement) has been created so the deprecation will appear in release posts and on the [general deprecation page](https://docs.gitlab.com/ee/update/deprecations).
- [ ] Documentation has been updated to mark the feature as [deprecated](https://docs.gitlab.com/ee/development/documentation/versions.html#deprecations-and-removals).
- [ ] On or before the major milestone: A [removal entry](https://about.gitlab.com/handbook/marketing/blog/release-posts/#removals) has been created so the removal will appear on the [removals by milestones](https://docs.gitlab.com/ee/update/removals) page and be announced in the release post.
- [ ] On or before the major milestone: A [removal entry](https://about.gitlab.com/handbook/marketing/blog/release-posts/#creating-the-announcement-1) has been created so the removal will appear on the [removals by milestones](https://docs.gitlab.com/ee/update/removals) page and be announced in the release post.
- On the major milestone:
- [ ] The deprecated item has been removed.
- [ ] If the removal of the deprecated item is a [breaking change](https://about.gitlab.com/handbook/product/gitlab-the-product/#examples-of-breaking-changes), the merge request is labeled ~"breaking change".

View File

@ -95,7 +95,6 @@ Lint/RedundantCopDisableDirective:
- 'ee/app/controllers/ee/groups/group_members_controller.rb'
- 'ee/app/controllers/ee/projects/settings/ci_cd_controller.rb'
- 'ee/app/controllers/groups/todos_controller.rb'
- 'ee/app/experiments/cart_abandonment_modal_experiment.rb'
- 'ee/app/finders/epics/with_issues_finder.rb'
- 'ee/app/finders/geo/file_registry_finder.rb'
- 'ee/app/finders/geo/project_registry_finder.rb'

View File

@ -101,7 +101,6 @@ Style/EmptyMethod:
- 'ee/app/controllers/projects/security/sast_configuration_controller.rb'
- 'ee/app/controllers/projects/settings/slacks_controller.rb'
- 'ee/app/controllers/subscriptions/groups_controller.rb'
- 'ee/app/experiments/cart_abandonment_modal_experiment.rb'
- 'ee/app/models/ee/epic.rb'
- 'ee/app/services/feature_flag_issues/destroy_service.rb'
- 'ee/db/geo/migrate/20170906174622_remove_duplicates_from_project_registry.rb'

View File

@ -11330,6 +11330,7 @@ Describes a rule for who can approve merge requests.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="approvalruleallowmergewheninvalid"></a>`allowMergeWhenInvalid` | [`Boolean`](#boolean) | Indicates if the rule can be ignored if it is invalid. |
| <a id="approvalruleapprovalsrequired"></a>`approvalsRequired` | [`Int`](#int) | Number of required approvals. |
| <a id="approvalruleapproved"></a>`approved` | [`Boolean`](#boolean) | Indicates if the rule is satisfied. |
| <a id="approvalruleapprovedby"></a>`approvedBy` | [`UserCoreConnection`](#usercoreconnection) | List of users defined in the rule that approved the merge request. (see [Connections](#connections)) |
@ -11338,6 +11339,7 @@ Describes a rule for who can approve merge requests.
| <a id="approvalruleeligibleapprovers"></a>`eligibleApprovers` | [`[UserCore!]`](#usercore) | List of all users eligible to approve the merge request (defined explicitly and from associated groups). |
| <a id="approvalrulegroups"></a>`groups` | [`GroupConnection`](#groupconnection) | List of groups added as approvers for the rule. (see [Connections](#connections)) |
| <a id="approvalruleid"></a>`id` | [`GlobalID!`](#globalid) | ID of the rule. |
| <a id="approvalruleinvalid"></a>`invalid` | [`Boolean`](#boolean) | Indicates if the rule is invalid and cannot be approved. |
| <a id="approvalrulename"></a>`name` | [`String`](#string) | Name of the rule. |
| <a id="approvalruleoverridden"></a>`overridden` | [`Boolean`](#boolean) | Indicates if the rule was overridden for the merge request. |
| <a id="approvalrulesection"></a>`section` | [`String`](#string) | Named section of the Code Owners file that the rule applies to. |
@ -13052,6 +13054,7 @@ The deployment of an environment.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="deploymentapprovalsummary"></a>`approvalSummary` | [`DeploymentApprovalSummary`](#deploymentapprovalsummary) | Approval summary of the deployment.This field can only be resolved for one deployment in any single request. |
| <a id="deploymentapprovals"></a>`approvals` | [`[DeploymentApproval!]`](#deploymentapproval) | Current approvals of the deployment. |
| <a id="deploymentcommit"></a>`commit` | [`Commit`](#commit) | Commit details of the deployment. |
| <a id="deploymentcreatedat"></a>`createdAt` | [`Time`](#time) | When the deployment record was created. |
| <a id="deploymentfinishedat"></a>`finishedAt` | [`Time`](#time) | When the deployment finished. |

View File

@ -107,14 +107,24 @@ Without the approvals, the work cannot merge. Required approvals enable multiple
## Invalid rules
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334698) in GitLab 15.1.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334698) in GitLab 15.1.
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/389905) in GitLab 15.11 [with a flag](../../../../administration/feature_flags.md) named `invalid_scan_result_policy_prevents_merge`. Disabled by default.
Whenever an approval rule cannot be satisfied, the rule will be displayed as `Invalid`. This applies to the following conditions:
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance,
ask an administrator to [enable the feature flag](../../../../administration/feature_flags.md) named `invalid_scan_result_policy_prevents_merge`.
On GitLab.com, this feature is available but can be configured by GitLab.com administrators only.
Whenever an approval rule cannot be satisfied, the rule is displayed as `(!) Auto approved`. This applies to the following conditions:
- The only eligible approver is the author of the merge request.
- No eligible approvers (either groups or users) have been assigned to the approval rule.
These rules will be automatically approved to unblock their respective merge requests.
These rules will be automatically approved (fail-open state) to unblock their respective merge requests,
unless they were created through a security policy.
Invalid approval rules created through a security policy are presented with `(!) Action Required`
and are not automatically approved (fail-closed state), blocking their respective merge requests.
## Related topics

View File

@ -0,0 +1,74 @@
# frozen_string_literal: true
module Gitlab
module Graphql
module Loaders
class LazyRelationLoader
class << self
attr_accessor :model, :association
# Automatically register the inheriting
# classes to GitlabSchema as lazy objects.
def inherited(klass)
GitlabSchema.lazy_resolve(klass, :load)
end
end
def initialize(query_ctx, object, **kwargs)
@query_ctx = query_ctx
@object = object
@kwargs = kwargs
query_ctx[loader_cache_key] ||= Registry.new(relation(**kwargs))
query_ctx[loader_cache_key].register(object)
end
# Returns an instance of `RelationProxy` for the object (parent model).
# The returned object behaves like an Active Record relation to support
# keyset pagination.
def load
case reflection.macro
when :has_many
relation_proxy
when :has_one
relation_proxy.last
else
raise 'Not supported association type!'
end
end
private
attr_reader :query_ctx, :object, :kwargs
delegate :model, :association, to: :"self.class"
# Implement this one if you want to filter the relation
def relation(**)
base_relation
end
def loader_cache_key
@loader_cache_key ||= self.class.name.to_s + kwargs.sort.to_s
end
def base_relation
placeholder_record.association(association).scope
end
# This will only work for HasMany and HasOne associations for now
def placeholder_record
model.new(reflection.active_record_primary_key => 0)
end
def reflection
model.reflections[association.to_s]
end
def relation_proxy
RelationProxy.new(object, query_ctx[loader_cache_key])
end
end
end
end
end

View File

@ -0,0 +1,75 @@
# frozen_string_literal: true
module Gitlab
module Graphql
module Loaders
class LazyRelationLoader
class Registry
PrematureQueryExecutionTriggered = Class.new(RuntimeError)
# Following methods are Active Record kicker methods which fire SQL query.
# We can support some of them with TopNLoader but for now restricting their use
# as we don't have a use case.
PROHIBITED_METHODS = (
ActiveRecord::FinderMethods.instance_methods(false) +
ActiveRecord::Calculations.instance_methods(false)
).to_set.freeze
def initialize(relation)
@parents = []
@relation = relation
@records = []
@loaded = false
end
def register(object)
@parents << object
end
def method_missing(method_name, ...)
raise PrematureQueryExecutionTriggered if PROHIBITED_METHODS.include?(method_name)
result = relation.public_send(method_name, ...) # rubocop:disable GitlabSecurity/PublicSend
if result.is_a?(ActiveRecord::Relation) # Spawn methods generate a new relation (e.g. where, limit)
@relation = result
return self
end
result
end
def respond_to_missing?(method_name, include_private = false)
relation.respond_to?(method_name, include_private)
end
def load
return records if loaded
@loaded = true
@records = TopNLoader.load(relation, parents)
end
def for(object)
load.select { |record| record[foreign_key] == object[active_record_primary_key] }
.tap { |records| set_inverse_of(object, records) }
end
private
attr_reader :parents, :relation, :records, :loaded
delegate :proxy_association, to: :relation, private: true
delegate :reflection, to: :proxy_association, private: true
delegate :active_record_primary_key, :foreign_key, to: :reflection, private: true
def set_inverse_of(object, records)
records.each do |record|
object.association(reflection.name).set_inverse_instance(record)
end
end
end
end
end
end
end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
module Gitlab
module Graphql
module Loaders
class LazyRelationLoader
# Proxies all the method calls to Registry instance.
# The main purpose of having this is that calling load
# on an instance of this class will only return the records
# associated with the main Active Record model.
class RelationProxy
def initialize(object, registry)
@object = object
@registry = registry
end
def load
registry.for(object)
end
alias_method :to_a, :load
def last(limit = 1)
result = registry.limit(limit)
.reverse_order!
.for(object)
return result.first if limit == 1 # This is the Active Record behavior
result
end
private
attr_reader :registry, :object
# Delegate everything to registry
def method_missing(method_name, ...)
result = registry.public_send(method_name, ...) # rubocop:disable GitlabSecurity/PublicSend
return self if result == registry
result
end
def respond_to_missing?(method_name, include_private = false)
registry.respond_to?(method_name, include_private)
end
end
end
end
end
end

View File

@ -0,0 +1,83 @@
# frozen_string_literal: true
# rubocop:disable CodeReuse/ActiveRecord
module Gitlab
module Graphql
module Loaders
class LazyRelationLoader
# Loads the top-n records for each given parent record.
# For example; if you want to load only 5 confidential issues ordered by
# their updated_at column per project for a list of projects by issuing only a single
# SQL query then this class can help you.
# Note that the limit applies per parent record which means that if you apply limit as 5
# for 10 projects, this loader will load 50 records in total.
class TopNLoader
def self.load(original_relation, parents)
new(original_relation, parents).load
end
def initialize(original_relation, parents)
@original_relation = original_relation
@parents = parents
end
def load
klass.select(klass.arel_table[Arel.star])
.from(from)
.joins("JOIN LATERAL (#{lateral_relation.to_sql}) AS #{klass.arel_table.name} ON true")
.includes(original_includes)
.preload(original_preload)
.eager_load(original_eager_load)
.load
end
private
attr_reader :original_relation, :parents
delegate :proxy_association, to: :original_relation, private: true
delegate :reflection, to: :proxy_association, private: true
delegate :klass, :foreign_key, :active_record, :active_record_primary_key,
to: :reflection, private: true
# This only works for HasMany and HasOne.
def lateral_relation
original_relation
.unscope(where: foreign_key) # unscoping the where condition generated for the placeholder_record.
.where(klass.arel_table[foreign_key].eq(active_record.arel_table[active_record_primary_key]))
end
def from
grouping_arel_node.as("#{active_record.arel_table.name}(#{active_record.primary_key})")
end
def grouping_arel_node
Arel::Nodes::Grouping.new(id_list_arel_node)
end
def id_list_arel_node
parent_ids.map { |id| [id] }
.then { |ids| Arel::Nodes::ValuesList.new(ids) }
end
def parent_ids
parents.pluck(active_record.primary_key)
end
def original_includes
original_relation.includes_values
end
def original_preload
original_relation.preload_values
end
def original_eager_load
original_relation.eager_load_values
end
end
end
end
end
end
# rubocop:enable CodeReuse/ActiveRecord

View File

@ -13,6 +13,10 @@ module Gitlab
ActiveRecord::Relation,
Gitlab::Graphql::Pagination::Keyset::Connection)
schema.connections.add(
Gitlab::Graphql::Loaders::LazyRelationLoader::RelationProxy,
Gitlab::Graphql::Pagination::Keyset::Connection)
schema.connections.add(
Gitlab::Graphql::ExternallyPaginatedArray,
Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection)

View File

@ -13007,6 +13007,9 @@ msgstr ""
msgid "CycleAnalytics|New value stream…"
msgstr ""
msgid "CycleAnalytics|No data"
msgstr ""
msgid "CycleAnalytics|Number of tasks"
msgstr ""
@ -42810,12 +42813,6 @@ msgstr ""
msgid "Subscriptions|Activation date"
msgstr ""
msgid "Subscriptions|Chat with sales"
msgstr ""
msgid "Subscriptions|Close"
msgstr ""
msgid "Subscriptions|End date"
msgstr ""
@ -42828,18 +42825,9 @@ msgstr ""
msgid "Subscriptions|None"
msgstr ""
msgid "Subscriptions|Not ready to buy yet?"
msgstr ""
msgid "Subscriptions|Start a free trial"
msgstr ""
msgid "Subscriptions|Start date"
msgstr ""
msgid "Subscriptions|We understand. Maybe you have some questions for our sales team, or maybe you'd like to try some of the paid features first. What would you like to do?"
msgstr ""
msgid "Subscription|Your subscription for %{strong}%{namespace_name}%{strong_close} has expired and you are now on %{pricing_link_start}the GitLab Free tier%{pricing_link_end}. Don't worry, your data is safe. Get in touch with our support team (%{support_email}). They'll gladly help with your subscription renewal."
msgstr ""

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Loaders::LazyRelationLoader::Registry, feature_category: :vulnerability_management do
describe '#respond_to?' do
let(:relation) { Project.all }
let(:registry) { described_class.new(relation) }
subject { registry.respond_to?(method_name) }
context 'when the relation responds to given method' do
let(:method_name) { :sorted_by_updated_asc }
it { is_expected.to be_truthy }
end
context 'when the relation does not respond to given method' do
let(:method_name) { :this_method_does_not_exist }
it { is_expected.to be_falsey }
end
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Loaders::LazyRelationLoader::RelationProxy, feature_category: :vulnerability_management do
describe '#respond_to?' do
let(:object) { double }
let(:registry) { instance_double(Gitlab::Graphql::Loaders::LazyRelationLoader::Registry) }
let(:relation_proxy) { described_class.new(object, registry) }
subject { relation_proxy.respond_to?(:foo) }
before do
allow(registry).to receive(:respond_to?).with(:foo, false).and_return(responds_to?)
end
context 'when the registry responds to given method' do
let(:responds_to?) { true }
it { is_expected.to be_truthy }
end
context 'when the registry does not respond to given method' do
let(:responds_to?) { false }
it { is_expected.to be_falsey }
end
end
end

View File

@ -0,0 +1,123 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Loaders::LazyRelationLoader, feature_category: :vulnerability_management do
let(:query_context) { {} }
let(:args) { {} }
let_it_be(:project) { create(:project) }
let(:loader) { loader_class.new(query_context, project, **args) }
describe '#load' do
subject(:load_relation) { loader.load }
context 'when the association is has many' do
let_it_be(:public_issue) { create(:issue, project: project) }
let_it_be(:confidential_issue) { create(:issue, :confidential, project: project) }
let(:loader_class) do
Class.new(described_class) do
self.model = Project
self.association = :issues
def relation(public_only: false)
relation = base_relation
relation = relation.public_only if public_only
relation
end
end
end
it { is_expected.to be_an_instance_of(described_class::RelationProxy) }
describe '#relation' do
subject { load_relation.load }
context 'without arguments' do
it { is_expected.to contain_exactly(public_issue, confidential_issue) }
end
context 'with arguments' do
let(:args) { { public_only: true } }
it { is_expected.to contain_exactly(public_issue) }
end
end
describe 'using the same context for different records' do
let_it_be(:another_project) { create(:project) }
let(:loader_for_another_project) { loader_class.new(query_context, another_project, **args) }
let(:records_for_another_project) { loader_for_another_project.load.load }
let(:records_for_project) { load_relation.load }
before do
loader # register the original loader to query context
end
it 'does not mix associated records' do
expect(records_for_another_project).to be_empty
expect(records_for_project).to contain_exactly(public_issue, confidential_issue)
end
it 'does not cause N+1 queries' do
expect { records_for_another_project }.not_to exceed_query_limit(1)
end
end
describe 'using Active Record querying methods' do
subject { load_relation.limit(1).load.count }
it { is_expected.to be(1) }
end
describe 'using Active Record finder methods' do
subject { load_relation.last(2) }
it { is_expected.to contain_exactly(public_issue, confidential_issue) }
end
describe 'calling a method that returns a non relation object' do
subject { load_relation.limit(1).limit_value }
it { is_expected.to be(1) }
end
describe 'calling a prohibited method' do
subject(:count) { load_relation.count }
it 'raises a `PrematureQueryExecutionTriggered` error' do
expect { count }.to raise_error(described_class::Registry::PrematureQueryExecutionTriggered)
end
end
end
context 'when the association is has one' do
let!(:project_setting) { create(:project_setting, project: project) }
let(:loader_class) do
Class.new(described_class) do
self.model = Project
self.association = :project_setting
end
end
it { is_expected.to eq(project_setting) }
end
context 'when the association is belongs to' do
let(:loader_class) do
Class.new(described_class) do
self.model = Project
self.association = :namespace
end
end
it 'raises error' do
expect { load_relation }.to raise_error(RuntimeError)
end
end
end
end