summaryrefslogtreecommitdiff
path: root/app/models/concerns
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-04-20 10:00:54 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-04-20 10:00:54 +0000
commit3cccd102ba543e02725d247893729e5c73b38295 (patch)
treef36a04ec38517f5deaaacb5acc7d949688d1e187 /app/models/concerns
parent205943281328046ef7b4528031b90fbda70c75ac (diff)
downloadgitlab-ce-3cccd102ba543e02725d247893729e5c73b38295.tar.gz
Add latest changes from gitlab-org/gitlab@14-10-stable-eev14.10.0-rc42
Diffstat (limited to 'app/models/concerns')
-rw-r--r--app/models/concerns/batch_nullify_dependent_associations.rb27
-rw-r--r--app/models/concerns/bulk_users_by_email_load.rb24
-rw-r--r--app/models/concerns/featurable.rb38
-rw-r--r--app/models/concerns/from_set_operator.rb7
-rw-r--r--app/models/concerns/issuable.rb21
-rw-r--r--app/models/concerns/issuable_link.rb2
-rw-r--r--app/models/concerns/metric_image_uploading.rb54
-rw-r--r--app/models/concerns/sensitive_serializable_hash.rb7
-rw-r--r--app/models/concerns/spammable.rb3
-rw-r--r--app/models/concerns/taskable.rb8
10 files changed, 171 insertions, 20 deletions
diff --git a/app/models/concerns/batch_nullify_dependent_associations.rb b/app/models/concerns/batch_nullify_dependent_associations.rb
new file mode 100644
index 00000000000..c95b5b64a43
--- /dev/null
+++ b/app/models/concerns/batch_nullify_dependent_associations.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+# Provides a way to execute nullify behaviour in batches
+# to avoid query timeouts for really big tables
+# Assumes that associations have `dependent: :nullify` statement
+module BatchNullifyDependentAssociations
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def dependent_associations_to_nullify
+ reflect_on_all_associations(:has_many).select { |assoc| assoc.options[:dependent] == :nullify }
+ end
+ end
+
+ def nullify_dependent_associations_in_batches(exclude: [], batch_size: 100)
+ self.class.dependent_associations_to_nullify.each do |association|
+ next if association.name.in?(exclude)
+
+ loop do
+ # rubocop:disable GitlabSecurity/PublicSend
+ update_count = public_send(association.name).limit(batch_size).update_all(association.foreign_key => nil)
+ # rubocop:enable GitlabSecurity/PublicSend
+ break if update_count < batch_size
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/bulk_users_by_email_load.rb b/app/models/concerns/bulk_users_by_email_load.rb
new file mode 100644
index 00000000000..edbd3e21458
--- /dev/null
+++ b/app/models/concerns/bulk_users_by_email_load.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module BulkUsersByEmailLoad
+ extend ActiveSupport::Concern
+
+ included do
+ def users_by_emails(emails)
+ Gitlab::SafeRequestLoader.execute(resource_key: user_by_email_resource_key, resource_ids: emails) do |emails|
+ # have to consider all emails - even secondary, so use all_emails here
+ grouped_users_by_email = User.by_any_email(emails).preload(:emails).group_by(&:all_emails)
+
+ grouped_users_by_email.each_with_object({}) do |(found_emails, users), h|
+ found_emails.each { |e| h[e] = users.first if emails.include?(e) } # don't include all emails for an account, only the ones we want
+ end
+ end
+ end
+
+ private
+
+ def user_by_email_resource_key
+ "user_by_email_for_#{User.name.underscore.pluralize}:#{self.class}:#{self.id}"
+ end
+ end
+end
diff --git a/app/models/concerns/featurable.rb b/app/models/concerns/featurable.rb
index 70d67fc7559..08189d83534 100644
--- a/app/models/concerns/featurable.rb
+++ b/app/models/concerns/featurable.rb
@@ -50,7 +50,7 @@ module Featurable
end
def available_features
- @available_features
+ @available_features || []
end
def access_level_attribute(feature)
@@ -74,6 +74,12 @@ module Featurable
STRING_OPTIONS.key(level)
end
+ def required_minimum_access_level(feature)
+ ensure_feature!(feature)
+
+ Gitlab::Access::GUEST
+ end
+
def ensure_feature!(feature)
feature = feature.model_name.plural if feature.respond_to?(:model_name)
feature = feature.to_sym
@@ -91,8 +97,8 @@ module Featurable
public_send(self.class.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend
end
- def feature_available?(feature, user)
- get_permission(user, feature)
+ def feature_available?(feature, user = nil)
+ has_permission?(user, feature)
end
def string_access_level(feature)
@@ -115,4 +121,30 @@ module Featurable
def feature_validation_exclusion
[]
end
+
+ def has_permission?(user, feature)
+ case access_level(feature)
+ when DISABLED
+ false
+ when PRIVATE
+ member?(user, feature)
+ when ENABLED
+ true
+ when PUBLIC
+ true
+ else
+ true
+ end
+ end
+
+ def member?(user, feature)
+ return false unless user
+ return true if user.can_read_all_resources?
+
+ resource_member?(user, feature)
+ end
+
+ def resource_member?(user, feature)
+ raise NotImplementedError
+ end
end
diff --git a/app/models/concerns/from_set_operator.rb b/app/models/concerns/from_set_operator.rb
index c6d63631c84..ce3a83e9fa1 100644
--- a/app/models/concerns/from_set_operator.rb
+++ b/app/models/concerns/from_set_operator.rb
@@ -11,7 +11,12 @@ module FromSetOperator
raise "Trying to redefine method '#{method(method_name)}'" if methods.include?(method_name)
define_method(method_name) do |members, remove_duplicates: true, remove_order: true, alias_as: table_name|
- operator_sql = operator.new(members, remove_duplicates: remove_duplicates, remove_order: remove_order).to_sql
+ operator_sql =
+ if members.any?
+ operator.new(members, remove_duplicates: remove_duplicates, remove_order: remove_order).to_sql
+ else
+ where("1=0").to_sql
+ end
from(Arel.sql("(#{operator_sql}) #{alias_as}"))
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 1eb30e88f16..dbd760a9c45 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -195,7 +195,7 @@ module Issuable
end
def supports_escalation?
- return false unless ::Feature.enabled?(:incident_escalations, project)
+ return false unless ::Feature.enabled?(:incident_escalations, project, default_enabled: :yaml)
incident?
end
@@ -318,12 +318,16 @@ module Issuable
# 2. We can't ORDER BY a column that isn't in the GROUP BY and doesn't
# have an aggregate function applied, so we do a useless MIN() instead.
#
- milestones_due_date = 'MIN(milestones.due_date)'
+ milestones_due_date = Milestone.arel_table[:due_date].minimum
+ milestones_due_date_with_direction = direction == 'ASC' ? milestones_due_date.asc : milestones_due_date.desc
+
+ highest_priority_arel = Arel.sql('highest_priority')
+ highest_priority_arel_with_direction = direction == 'ASC' ? highest_priority_arel.asc : highest_priority_arel.desc
order_milestone_due_asc
.order_labels_priority(excluded_labels: excluded_labels, extra_select_columns: [milestones_due_date])
- .reorder(Gitlab::Database.nulls_last_order(milestones_due_date, direction),
- Gitlab::Database.nulls_last_order('highest_priority', direction))
+ .reorder(milestones_due_date_with_direction.nulls_last,
+ highest_priority_arel_with_direction.nulls_last)
end
def order_labels_priority(direction = 'ASC', excluded_labels: [], extra_select_columns: [], with_cte: false)
@@ -341,12 +345,15 @@ module Issuable
extra_select_columns.unshift("highest_priorities.label_priority as highest_priority")
+ highest_priority_arel = Arel.sql('highest_priority')
+ highest_priority_arel_with_direction = direction == 'ASC' ? highest_priority_arel.asc : highest_priority_arel.desc
+
select(issuable_columns)
.select(extra_select_columns)
.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))
+ .reorder(highest_priority_arel_with_direction.nulls_last)
end
def with_label(title, sort = nil)
@@ -524,6 +531,10 @@ module Issuable
labels.order('title ASC').pluck(:title)
end
+ def labels_hook_attrs
+ labels.map(&:hook_attrs)
+ end
+
# Convert this Issuable class name to a format usable by Ability definitions
#
# Examples:
diff --git a/app/models/concerns/issuable_link.rb b/app/models/concerns/issuable_link.rb
index 3e14507bc70..c319d685362 100644
--- a/app/models/concerns/issuable_link.rb
+++ b/app/models/concerns/issuable_link.rb
@@ -29,6 +29,8 @@ module IssuableLink
validate :check_self_relation
validate :check_opposite_relation
+ scope :for_source_or_target, ->(issuable) { where(source: issuable).or(where(target: issuable)) }
+
enum link_type: { TYPE_RELATES_TO => 0, TYPE_BLOCKS => 1 }
private
diff --git a/app/models/concerns/metric_image_uploading.rb b/app/models/concerns/metric_image_uploading.rb
new file mode 100644
index 00000000000..3f7797f56c5
--- /dev/null
+++ b/app/models/concerns/metric_image_uploading.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module MetricImageUploading
+ extend ActiveSupport::Concern
+
+ MAX_FILE_SIZE = 1.megabyte.freeze
+
+ included do
+ include Gitlab::FileTypeDetection
+ include FileStoreMounter
+ include WithUploads
+
+ validates :file, presence: true
+ validate :validate_file_is_image
+ validates :url, length: { maximum: 255 }, public_url: { allow_blank: true }
+ validates :url_text, length: { maximum: 128 }
+
+ scope :order_created_at_asc, -> { order(created_at: :asc) }
+
+ attribute :file_store, :integer, default: -> { MetricImageUploader.default_store }
+
+ mount_file_store_uploader MetricImageUploader
+ end
+
+ def filename
+ @filename ||= file&.filename
+ end
+
+ def file_path
+ @file_path ||= begin
+ return file&.url unless file&.upload
+
+ # If we're using a CDN, we need to use the full URL
+ asset_host = ActionController::Base.asset_host || Gitlab.config.gitlab.base_url
+
+ Gitlab::Utils.append_path(asset_host, local_path)
+ end
+ end
+
+ private
+
+ def valid_file_extensions
+ Gitlab::FileTypeDetection::SAFE_IMAGE_EXT
+ end
+
+ def validate_file_is_image
+ unless image?
+ message = _('does not have a supported extension. Only %{extension_list} are supported') % {
+ extension_list: valid_file_extensions.to_sentence
+ }
+ errors.add(:file, message)
+ end
+ end
+end
diff --git a/app/models/concerns/sensitive_serializable_hash.rb b/app/models/concerns/sensitive_serializable_hash.rb
index 725ec60e9b6..94451fcd2c2 100644
--- a/app/models/concerns/sensitive_serializable_hash.rb
+++ b/app/models/concerns/sensitive_serializable_hash.rb
@@ -19,7 +19,6 @@ module SensitiveSerializableHash
# In general, prefer NOT to use serializable_hash / to_json / as_json in favor
# of serializers / entities instead which has an allowlist of attributes
def serializable_hash(options = nil)
- return super unless prevent_sensitive_fields_from_serializable_hash?
return super if options && options[:unsafe_serialization_hash]
options = options.try(:dup) || {}
@@ -37,10 +36,4 @@ module SensitiveSerializableHash
super(options)
end
-
- private
-
- def prevent_sensitive_fields_from_serializable_hash?
- Feature.enabled?(:prevent_sensitive_fields_from_serializable_hash, default_enabled: :yaml)
- end
end
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index b475eb79aa3..d27b451892a 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -84,7 +84,8 @@ module Spammable
end
def unrecoverable_spam_error!
- self.errors.add(:base, "Your #{spammable_entity_type} has been recognized as spam and has been discarded.")
+ self.errors.add(:base, _("Your %{spammable_entity_type} has been recognized as spam and has been discarded.") \
+ % { spammable_entity_type: spammable_entity_type })
end
def spammable_entity_type
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index e41a0ca28f9..904c96b11b3 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -11,14 +11,16 @@ require 'task_list/filter'
module Taskable
COMPLETED = 'completed'
INCOMPLETE = 'incomplete'
- COMPLETE_PATTERN = /(\[[xX]\])/.freeze
- INCOMPLETE_PATTERN = /(\[\s\])/.freeze
+ COMPLETE_PATTERN = /\[[xX]\]/.freeze
+ INCOMPLETE_PATTERN = /\[[[:space:]]\]/.freeze
ITEM_PATTERN = %r{
^
(?:(?:>\s{0,4})*) # optional blockquote characters
((?:\s*(?:[-+*]|(?:\d+\.)))+) # list prefix (one or more) required - task item has to be always in a list
\s+ # whitespace prefix has to be always presented for a list item
- (\[\s\]|\[[xX]\]) # checkbox
+ ( # checkbox
+ #{COMPLETE_PATTERN}|#{INCOMPLETE_PATTERN}
+ )
(\s.+) # followed by whitespace and some text.
}x.freeze