Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/concerns')
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stage.rb4
-rw-r--r--app/models/concerns/any_field_validation.rb25
-rw-r--r--app/models/concerns/approvable_base.rb13
-rw-r--r--app/models/concerns/atomic_internal_id.rb23
-rw-r--r--app/models/concerns/avatarable.rb7
-rw-r--r--app/models/concerns/cache_markdown_field.rb6
-rw-r--r--app/models/concerns/cascading_namespace_setting_attribute.rb13
-rw-r--r--app/models/concerns/ci/maskable.rb2
-rw-r--r--app/models/concerns/ci/metadatable.rb2
-rw-r--r--app/models/concerns/enums/ci/commit_status.rb1
-rw-r--r--app/models/concerns/has_integrations.rb2
-rw-r--r--app/models/concerns/integrations/has_web_hook.rb36
-rw-r--r--app/models/concerns/issuable.rb30
-rw-r--r--app/models/concerns/milestoneish.rb4
-rw-r--r--app/models/concerns/partitioned_table.rb6
-rw-r--r--app/models/concerns/sortable.rb2
-rw-r--r--app/models/concerns/taggable_queries.rb21
17 files changed, 149 insertions, 48 deletions
diff --git a/app/models/concerns/analytics/cycle_analytics/stage.rb b/app/models/concerns/analytics/cycle_analytics/stage.rb
index 90d48aa81d0..2a0274f5706 100644
--- a/app/models/concerns/analytics/cycle_analytics/stage.rb
+++ b/app/models/concerns/analytics/cycle_analytics/stage.rb
@@ -50,6 +50,10 @@ module Analytics
end
end
+ def events_hash_code
+ Digest::SHA256.hexdigest("#{start_event.hash_code}-#{end_event.hash_code}")
+ end
+
def start_event_label_based?
start_event_identifier && start_event.label_based?
end
diff --git a/app/models/concerns/any_field_validation.rb b/app/models/concerns/any_field_validation.rb
new file mode 100644
index 00000000000..987c4e7800e
--- /dev/null
+++ b/app/models/concerns/any_field_validation.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# This module enables a record to be valid if any field is present
+#
+# Overwrite one_of_required_fields to set one of which fields must be present
+module AnyFieldValidation
+ extend ActiveSupport::Concern
+
+ included do
+ validate :any_field_present
+ end
+
+ private
+
+ def any_field_present
+ return unless one_of_required_fields.all? { |field| self[field].blank? }
+
+ errors.add(:base, _("At least one field of %{one_of_required_fields} must be present") %
+ { one_of_required_fields: one_of_required_fields })
+ end
+
+ def one_of_required_fields
+ raise NotImplementedError
+ end
+end
diff --git a/app/models/concerns/approvable_base.rb b/app/models/concerns/approvable_base.rb
index c2d94b50f8d..ef7ba7b1089 100644
--- a/app/models/concerns/approvable_base.rb
+++ b/app/models/concerns/approvable_base.rb
@@ -24,6 +24,19 @@ module ApprovableBase
.group(:id)
.having("COUNT(users.id) = ?", usernames.size)
end
+
+ scope :not_approved_by_users_with_usernames, -> (usernames) do
+ users = User.where(username: usernames).select(:id)
+ self_table = self.arel_table
+ app_table = Approval.arel_table
+
+ where(
+ Approval.where(approvals: { user_id: users })
+ .where(app_table[:merge_request_id].eq(self_table[:id]))
+ .select('true')
+ .arel.exists.not
+ )
+ end
end
class_methods do
diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb
index 80cf6260b0b..88f577c3e23 100644
--- a/app/models/concerns/atomic_internal_id.rb
+++ b/app/models/concerns/atomic_internal_id.rb
@@ -159,9 +159,8 @@ module AtomicInternalId
# Defines class methods:
#
# - with_{scope}_{column}_supply
- # This method can be used to allocate a block of IID values during
- # bulk operations (importing/copying, etc). This can be more efficient
- # than creating instances one-by-one.
+ # This method can be used to allocate a stream of IID values during
+ # bulk operations (importing/copying, etc).
#
# Pass in a block that receives a `Supply` instance. To allocate a new
# IID value, call `Supply#next_value`.
@@ -181,14 +180,8 @@ module AtomicInternalId
scope_attrs = ::AtomicInternalId.scope_attrs(scope_value)
usage = ::AtomicInternalId.scope_usage(self)
- generator = InternalId::InternalIdGenerator.new(subject, scope_attrs, usage, init)
-
- generator.with_lock do
- supply = Supply.new(generator.record.last_value)
- block.call(supply)
- ensure
- generator.track_greatest(supply.current_value) if supply
- end
+ supply = Supply.new(-> { InternalId.generate_next(subject, scope_attrs, usage, init) })
+ block.call(supply)
end
end
end
@@ -236,14 +229,14 @@ module AtomicInternalId
end
class Supply
- attr_reader :current_value
+ attr_reader :generator
- def initialize(start_value)
- @current_value = start_value
+ def initialize(generator)
+ @generator = generator
end
def next_value
- @current_value += 1
+ @generator.call
end
end
end
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index fdc418029be..84a74386ff7 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -9,13 +9,18 @@ module Avatarable
ALLOWED_IMAGE_SCALER_WIDTHS = (USER_AVATAR_SIZES | PROJECT_AVATAR_SIZES | GROUP_AVATAR_SIZES).freeze
+ # This value must not be bigger than then: https://gitlab.com/gitlab-org/gitlab/-/blob/master/workhorse/config.toml.example#L20
+ #
+ # https://docs.gitlab.com/ee/development/image_scaling.html
+ MAXIMUM_FILE_SIZE = 200.kilobytes.to_i
+
included do
prepend ShadowMethods
include ObjectStorage::BackgroundMove
include Gitlab::Utils::StrongMemoize
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
- validates :avatar, file_size: { maximum: 200.kilobytes.to_i }, if: :avatar_changed?
+ validates :avatar, file_size: { maximum: MAXIMUM_FILE_SIZE }, if: :avatar_changed?
mount_uploader :avatar, AvatarUploader
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 101bff32dfe..79b622c8dad 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -163,9 +163,9 @@ module CacheMarkdownField
refs = all_references(self.author)
references = {}
- references[:mentioned_users_ids] = refs.mentioned_users&.pluck(:id).presence
- references[:mentioned_groups_ids] = refs.mentioned_groups&.pluck(:id).presence
- references[:mentioned_projects_ids] = refs.mentioned_projects&.pluck(:id).presence
+ references[:mentioned_users_ids] = refs.mentioned_user_ids.presence
+ references[:mentioned_groups_ids] = refs.mentioned_group_ids.presence
+ references[:mentioned_projects_ids] = refs.mentioned_project_ids.presence
# One retry is enough as next time `model_user_mention` should return the existing mention record,
# that threw the `ActiveRecord::RecordNotUnique` exception in first place.
diff --git a/app/models/concerns/cascading_namespace_setting_attribute.rb b/app/models/concerns/cascading_namespace_setting_attribute.rb
index 9efd90756b1..5d24e15d518 100644
--- a/app/models/concerns/cascading_namespace_setting_attribute.rb
+++ b/app/models/concerns/cascading_namespace_setting_attribute.rb
@@ -24,10 +24,6 @@ module CascadingNamespaceSettingAttribute
include Gitlab::Utils::StrongMemoize
class_methods do
- def cascading_settings_feature_enabled?
- ::Feature.enabled?(:cascading_namespace_settings, default_enabled: true)
- end
-
private
# Facilitates the cascading lookup of values and,
@@ -82,8 +78,6 @@ module CascadingNamespaceSettingAttribute
def define_attr_reader(attribute)
define_method(attribute) do
strong_memoize(attribute) do
- next self[attribute] unless self.class.cascading_settings_feature_enabled?
-
next self[attribute] if will_save_change_to_attribute?(attribute)
next locked_value(attribute) if cascading_attribute_locked?(attribute, include_self: false)
next self[attribute] unless self[attribute].nil?
@@ -189,7 +183,6 @@ module CascadingNamespaceSettingAttribute
end
def locked_ancestor(attribute)
- return unless self.class.cascading_settings_feature_enabled?
return unless namespace.has_parent?
strong_memoize(:"#{attribute}_locked_ancestor") do
@@ -202,14 +195,10 @@ module CascadingNamespaceSettingAttribute
end
def locked_by_ancestor?(attribute)
- return false unless self.class.cascading_settings_feature_enabled?
-
locked_ancestor(attribute).present?
end
def locked_by_application_setting?(attribute)
- return false unless self.class.cascading_settings_feature_enabled?
-
Gitlab::CurrentSettings.public_send("lock_#{attribute}") # rubocop:disable GitlabSecurity/PublicSend
end
@@ -241,7 +230,7 @@ module CascadingNamespaceSettingAttribute
def namespace_ancestor_ids
strong_memoize(:namespace_ancestor_ids) do
- namespace.self_and_ancestors(hierarchy_order: :asc).pluck(:id).reject { |id| id == namespace_id }
+ namespace.ancestor_ids(hierarchy_order: :asc)
end
end
diff --git a/app/models/concerns/ci/maskable.rb b/app/models/concerns/ci/maskable.rb
index e1ef4531845..62be0150ee0 100644
--- a/app/models/concerns/ci/maskable.rb
+++ b/app/models/concerns/ci/maskable.rb
@@ -11,7 +11,7 @@ module Ci
# * Minimal length of 8 characters
# * Characters must be from the Base64 alphabet (RFC4648) with the addition of '@', ':', '.', and '~'
# * Absolutely no fun is allowed
- REGEX = /\A[a-zA-Z0-9_+=\/@:.~-]{8,}\z/.freeze
+ REGEX = %r{\A[a-zA-Z0-9_+=/@:.~-]{8,}\z}.freeze
included do
validates :masked, inclusion: { in: [true, false] }
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index 601637ea32a..114435d5a21 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -77,7 +77,7 @@ module Ci
def write_metadata_attribute(legacy_key, metadata_key, value)
# save to metadata or this model depending on the state of feature flag
- if Feature.enabled?(:ci_build_metadata_config)
+ if Feature.enabled?(:ci_build_metadata_config, project, default_enabled: :yaml)
ensure_metadata.write_attribute(metadata_key, value)
write_attribute(legacy_key, nil)
else
diff --git a/app/models/concerns/enums/ci/commit_status.rb b/app/models/concerns/enums/ci/commit_status.rb
index 72788d15c0a..16dec5fb081 100644
--- a/app/models/concerns/enums/ci/commit_status.rb
+++ b/app/models/concerns/enums/ci/commit_status.rb
@@ -25,6 +25,7 @@ module Enums
ci_quota_exceeded: 16,
pipeline_loop_detected: 17,
no_matching_runner: 18, # not used anymore, but cannot be deleted because of old data
+ trace_size_exceeded: 19,
insufficient_bridge_permissions: 1_001,
downstream_bridge_project_not_found: 1_002,
invalid_bridge_trigger: 1_003,
diff --git a/app/models/concerns/has_integrations.rb b/app/models/concerns/has_integrations.rb
index b2775f4cbb2..25650ae56ad 100644
--- a/app/models/concerns/has_integrations.rb
+++ b/app/models/concerns/has_integrations.rb
@@ -19,7 +19,7 @@ module HasIntegrations
def without_integration(integration)
integrations = Integration
.select('1')
- .where('services.project_id = projects.id')
+ .where("#{Integration.table_name}.project_id = projects.id")
.where(type: integration.type)
Project
diff --git a/app/models/concerns/integrations/has_web_hook.rb b/app/models/concerns/integrations/has_web_hook.rb
new file mode 100644
index 00000000000..dabe7152b18
--- /dev/null
+++ b/app/models/concerns/integrations/has_web_hook.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Integrations
+ module HasWebHook
+ extend ActiveSupport::Concern
+
+ included do
+ after_save :update_web_hook!, if: :activated?
+ end
+
+ # Return the URL to be used for the webhook.
+ def hook_url
+ raise NotImplementedError
+ end
+
+ # Return whether the webhook should use SSL verification.
+ def hook_ssl_verification
+ true
+ end
+
+ # Create or update the webhook, raising an exception if it cannot be saved.
+ def update_web_hook!
+ hook = service_hook || build_service_hook
+ hook.url = hook_url if hook.url != hook_url # avoid reencryption
+ hook.enable_ssl_verification = hook_ssl_verification
+ hook.save! if hook.changed?
+ hook
+ end
+
+ # Execute the webhook, creating it if necessary.
+ def execute_web_hook!(*args)
+ update_web_hook!
+ service_hook.execute(*args)
+ end
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 2d06247a486..d5e2e63402f 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -31,6 +31,7 @@ module Issuable
TITLE_HTML_LENGTH_MAX = 800
DESCRIPTION_LENGTH_MAX = 1.megabyte
DESCRIPTION_HTML_LENGTH_MAX = 5.megabytes
+ SEARCHABLE_FIELDS = %w(title description).freeze
STATE_ID_MAP = {
opened: 1,
@@ -264,15 +265,16 @@ module Issuable
# matched_columns - Modify the scope of the query. 'title', 'description' or joining them with a comma.
#
# Returns an ActiveRecord::Relation.
- def full_search(query, matched_columns: 'title,description', use_minimum_char_limit: true)
- allowed_columns = [:title, :description]
- matched_columns = matched_columns.to_s.split(',').map(&:to_sym)
- matched_columns &= allowed_columns
+ def full_search(query, matched_columns: nil, use_minimum_char_limit: true)
+ if matched_columns
+ matched_columns = matched_columns.to_s.split(',')
+ matched_columns &= SEARCHABLE_FIELDS
+ matched_columns.map!(&:to_sym)
+ end
- # Matching title or description if the matched_columns did not contain any allowed columns.
- matched_columns = [:title, :description] if matched_columns.empty?
+ search_columns = matched_columns.presence || [:title, :description]
- fuzzy_search(query, matched_columns, use_minimum_char_limit: use_minimum_char_limit)
+ fuzzy_search(query, search_columns, use_minimum_char_limit: use_minimum_char_limit)
end
def simple_sorts
@@ -330,12 +332,15 @@ module Issuable
# When using CTE make sure to select the same columns that are on the group_by clause.
# This prevents errors when ignored columns are present in the database.
issuable_columns = with_cte ? issue_grouping_columns(use_cte: with_cte) : "#{table_name}.*"
+ group_columns = issue_grouping_columns(use_cte: with_cte) + ["highest_priorities.label_priority"]
- extra_select_columns.unshift("(#{highest_priority}) AS highest_priority")
+ extra_select_columns.unshift("highest_priorities.label_priority as highest_priority")
select(issuable_columns)
.select(extra_select_columns)
- .group(issue_grouping_columns(use_cte: with_cte))
+ .from("#{table_name}")
+ .joins("JOIN LATERAL(#{highest_priority}) as highest_priorities ON TRUE")
+ .group(group_columns)
.reorder(Gitlab::Database.nulls_last_order('highest_priority', direction))
end
@@ -382,7 +387,7 @@ module Issuable
if use_cte
attribute_names.map { |attr| arel_table[attr.to_sym] }
else
- arel_table[:id]
+ [arel_table[:id]]
end
end
@@ -457,6 +462,7 @@ module Issuable
if old_associations
old_labels = old_associations.fetch(:labels, labels)
old_assignees = old_associations.fetch(:assignees, assignees)
+ old_severity = old_associations.fetch(:severity, severity)
if old_labels != labels
changes[:labels] = [old_labels.map(&:hook_attrs), labels.map(&:hook_attrs)]
@@ -466,6 +472,10 @@ module Issuable
changes[:assignees] = [old_assignees.map(&:hook_attrs), assignees.map(&:hook_attrs)]
end
+ if supports_severity? && old_severity != severity
+ changes[:severity] = [old_severity, severity]
+ end
+
if self.respond_to?(:total_time_spent)
old_total_time_spent = old_associations.fetch(:total_time_spent, total_time_spent)
old_time_change = old_associations.fetch(:time_change, time_change)
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index eaf64f2541d..4f2ea58f36d 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -101,6 +101,10 @@ module Milestoneish
due_date && due_date.past?
end
+ def expired
+ expired? || false
+ end
+
def total_time_spent
@total_time_spent ||= issues.joins(:timelogs).sum(:time_spent) + merge_requests.joins(:timelogs).sum(:time_spent)
end
diff --git a/app/models/concerns/partitioned_table.rb b/app/models/concerns/partitioned_table.rb
index 9f1cec5d520..eab5d4c35bb 100644
--- a/app/models/concerns/partitioned_table.rb
+++ b/app/models/concerns/partitioned_table.rb
@@ -10,12 +10,12 @@ module PartitionedTable
monthly: Gitlab::Database::Partitioning::MonthlyStrategy
}.freeze
- def partitioned_by(partitioning_key, strategy:)
+ def partitioned_by(partitioning_key, strategy:, **kwargs)
strategy_class = PARTITIONING_STRATEGIES[strategy.to_sym] || raise(ArgumentError, "Unknown partitioning strategy: #{strategy}")
- @partitioning_strategy = strategy_class.new(self, partitioning_key)
+ @partitioning_strategy = strategy_class.new(self, partitioning_key, **kwargs)
- Gitlab::Database::Partitioning::PartitionCreator.register(self)
+ Gitlab::Database::Partitioning::PartitionManager.register(self)
end
end
end
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index 9f5e9b2bb57..65fb62a814f 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -46,7 +46,7 @@ module Sortable
private
def highest_label_priority(target_type_column: nil, target_type: nil, target_column:, project_column:, excluded_labels: [])
- query = Label.select(LabelPriority.arel_table[:priority].minimum)
+ query = Label.select(LabelPriority.arel_table[:priority].minimum.as('label_priority'))
.left_join_priorities
.joins(:label_links)
.where("label_priorities.project_id = #{project_column}")
diff --git a/app/models/concerns/taggable_queries.rb b/app/models/concerns/taggable_queries.rb
index 2897e5e6420..cba2e93a86d 100644
--- a/app/models/concerns/taggable_queries.rb
+++ b/app/models/concerns/taggable_queries.rb
@@ -12,5 +12,26 @@ module TaggableQueries
.where(taggings: { context: context, taggable_type: polymorphic_name })
.select('COALESCE(array_agg(tags.name ORDER BY name), ARRAY[]::text[])')
end
+
+ def matches_tag_ids(tag_ids, table: quoted_table_name, column: 'id')
+ matcher = ::ActsAsTaggableOn::Tagging
+ .where(taggable_type: CommitStatus.name)
+ .where(context: 'tags')
+ .where("taggable_id = #{connection.quote_table_name(table)}.#{connection.quote_column_name(column)}") # rubocop:disable GitlabSecurity/SqlInjection
+ .where.not(tag_id: tag_ids)
+ .select('1')
+
+ where("NOT EXISTS (?)", matcher)
+ end
+
+ def with_any_tags(table: quoted_table_name, column: 'id')
+ matcher = ::ActsAsTaggableOn::Tagging
+ .where(taggable_type: CommitStatus.name)
+ .where(context: 'tags')
+ .where("taggable_id = #{connection.quote_table_name(table)}.#{connection.quote_column_name(column)}") # rubocop:disable GitlabSecurity/SqlInjection
+ .select('1')
+
+ where("EXISTS (?)", matcher)
+ end
end
end