diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 10:00:54 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 10:00:54 +0000 |
commit | 3cccd102ba543e02725d247893729e5c73b38295 (patch) | |
tree | f36a04ec38517f5deaaacb5acc7d949688d1e187 /app/models/concerns | |
parent | 205943281328046ef7b4528031b90fbda70c75ac (diff) | |
download | gitlab-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.rb | 27 | ||||
-rw-r--r-- | app/models/concerns/bulk_users_by_email_load.rb | 24 | ||||
-rw-r--r-- | app/models/concerns/featurable.rb | 38 | ||||
-rw-r--r-- | app/models/concerns/from_set_operator.rb | 7 | ||||
-rw-r--r-- | app/models/concerns/issuable.rb | 21 | ||||
-rw-r--r-- | app/models/concerns/issuable_link.rb | 2 | ||||
-rw-r--r-- | app/models/concerns/metric_image_uploading.rb | 54 | ||||
-rw-r--r-- | app/models/concerns/sensitive_serializable_hash.rb | 7 | ||||
-rw-r--r-- | app/models/concerns/spammable.rb | 3 | ||||
-rw-r--r-- | app/models/concerns/taskable.rb | 8 |
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 |