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_event_model.rb7
-rw-r--r--app/models/concerns/as_cte.rb12
-rw-r--r--app/models/concerns/async_devise_email.rb5
-rw-r--r--app/models/concerns/awardable.rb16
-rw-r--r--app/models/concerns/cache_markdown_field.rb7
-rw-r--r--app/models/concerns/ci/artifactable.rb2
-rw-r--r--app/models/concerns/enums/ci/pipeline.rb2
-rw-r--r--app/models/concerns/file_store_mounter.rb14
-rw-r--r--app/models/concerns/integrations/base_data_fields.rb29
-rw-r--r--app/models/concerns/integrations/has_data_fields.rb3
-rw-r--r--app/models/concerns/issuable.rb20
-rw-r--r--app/models/concerns/limitable.rb26
-rw-r--r--app/models/concerns/pg_full_text_searchable.rb11
-rw-r--r--app/models/concerns/project_features_compatibility.rb4
-rw-r--r--app/models/concerns/sensitive_serializable_hash.rb2
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb14
16 files changed, 124 insertions, 50 deletions
diff --git a/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb b/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
index 7cc4bc569d3..1bdb89349aa 100644
--- a/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
+++ b/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
@@ -33,9 +33,14 @@ module Analytics
)
duration_in_seconds = Arel::Nodes::Extract.new(duration, :epoch)
+ # start_event_timestamp and end_event_timestamp do not really influence the order,
+ # but are included so that they are part of the returned result, for example when
+ # using Gitlab::Analytics::CycleAnalytics::Aggregated::RecordsFetcher
keyset_order(
:total_time => { order_expression: arel_order(duration_in_seconds, direction), distinct: false, sql_type: 'double precision' },
- issuable_id_column => { order_expression: arel_order(arel_table[issuable_id_column], direction), distinct: true }
+ issuable_id_column => { order_expression: arel_order(arel_table[issuable_id_column], direction), distinct: true },
+ :end_event_timestamp => { order_expression: arel_order(arel_table[:end_event_timestamp], direction), distinct: true },
+ :start_event_timestamp => { order_expression: arel_order(arel_table[:start_event_timestamp], direction), distinct: true }
)
end
end
diff --git a/app/models/concerns/as_cte.rb b/app/models/concerns/as_cte.rb
new file mode 100644
index 00000000000..aa38ae3a9c1
--- /dev/null
+++ b/app/models/concerns/as_cte.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+# Convert any ActiveRecord::Relation to a Gitlab::SQL::CTE
+module AsCte
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def as_cte(name, **opts)
+ Gitlab::SQL::CTE.new(name, all, **opts)
+ end
+ end
+end
diff --git a/app/models/concerns/async_devise_email.rb b/app/models/concerns/async_devise_email.rb
index 38c99dc7e71..7cdbed2eef6 100644
--- a/app/models/concerns/async_devise_email.rb
+++ b/app/models/concerns/async_devise_email.rb
@@ -2,6 +2,7 @@
module AsyncDeviseEmail
extend ActiveSupport::Concern
+ include AfterCommitQueue
private
@@ -9,6 +10,8 @@ module AsyncDeviseEmail
def send_devise_notification(notification, *args)
return true unless can?(:receive_notifications)
- devise_mailer.__send__(notification, self, *args).deliver_later # rubocop:disable GitlabSecurity/PublicSend
+ run_after_commit_or_now do
+ devise_mailer.__send__(notification, self, *args).deliver_later # rubocop:disable GitlabSecurity/PublicSend
+ end
end
end
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index 896f0916d8c..1d0ce594f63 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -18,7 +18,7 @@ module Awardable
inner_query = award_emoji_table
.project('true')
.where(award_emoji_table[:user_id].eq(user.id))
- .where(award_emoji_table[:awardable_type].eq(self.name))
+ .where(award_emoji_table[:awardable_type].eq(base_class.name))
.where(award_emoji_table[:awardable_id].eq(self.arel_table[:id]))
inner_query = inner_query.where(award_emoji_table[:name].eq(name)) if name.present?
@@ -31,7 +31,7 @@ module Awardable
inner_query = award_emoji_table
.project('true')
.where(award_emoji_table[:user_id].eq(user.id))
- .where(award_emoji_table[:awardable_type].eq(self.name))
+ .where(award_emoji_table[:awardable_type].eq(base_class.name))
.where(award_emoji_table[:awardable_id].eq(self.arel_table[:id]))
inner_query = inner_query.where(award_emoji_table[:name].eq(name)) if name.present?
@@ -56,13 +56,11 @@ module Awardable
awardable_table = self.arel_table
awards_table = AwardEmoji.arel_table
- join_clause = awardable_table.join(awards_table, Arel::Nodes::OuterJoin).on(
- awards_table[:awardable_id].eq(awardable_table[:id]).and(
- awards_table[:awardable_type].eq(self.name).and(
- awards_table[:name].eq(emoji_name)
- )
- )
- ).join_sources
+ join_clause = awardable_table
+ .join(awards_table, Arel::Nodes::OuterJoin)
+ .on(awards_table[:awardable_id].eq(awardable_table[:id])
+ .and(awards_table[:awardable_type].eq(base_class.name).and(awards_table[:name].eq(emoji_name))))
+ .join_sources
joins(join_clause).group(awardable_table[:id]).reorder(
Arel.sql("COUNT(award_emoji.id) #{direction}")
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 9414d16beef..99dbe464a7c 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -24,6 +24,9 @@ module CacheMarkdownField
true
end
+ attr_accessor :skip_markdown_cache_validation
+ alias_method :skip_markdown_cache_validation?, :skip_markdown_cache_validation
+
# Returns the default Banzai render context for the cached markdown field.
def banzai_render_context(field)
raise ArgumentError, "Unknown field: #{field.inspect}" unless
@@ -91,7 +94,7 @@ module CacheMarkdownField
end
def invalidated_markdown_cache?
- cached_markdown_fields.html_fields.any? {|html_field| attribute_invalidated?(html_field) }
+ cached_markdown_fields.html_fields.any? { |html_field| attribute_invalidated?(html_field) }
end
def attribute_invalidated?(attr)
@@ -218,6 +221,8 @@ module CacheMarkdownField
# The HTML becomes invalid if any dependent fields change. For now, assume
# author and project invalidate the cache in all circumstances.
define_method(invalidation_method) do
+ return false if skip_markdown_cache_validation?
+
changed_fields = changed_attributes.keys
invalidations = changed_fields & [markdown_field.to_s, *INVALIDATED_BY]
!invalidations.empty? || !cached_html_up_to_date?(markdown_field)
diff --git a/app/models/concerns/ci/artifactable.rb b/app/models/concerns/ci/artifactable.rb
index 27040a677ff..78340cf967b 100644
--- a/app/models/concerns/ci/artifactable.rb
+++ b/app/models/concerns/ci/artifactable.rb
@@ -21,7 +21,7 @@ module Ci
}, _suffix: true
scope :expired_before, -> (timestamp) { where(arel_table[:expire_at].lt(timestamp)) }
- scope :expired, -> (limit) { expired_before(Time.current).limit(limit) }
+ scope :expired, -> { expired_before(Time.current) }
scope :project_id_in, ->(ids) { where(project_id: ids) }
end
diff --git a/app/models/concerns/enums/ci/pipeline.rb b/app/models/concerns/enums/ci/pipeline.rb
index 94d11c871ca..8ed6c54441b 100644
--- a/app/models/concerns/enums/ci/pipeline.rb
+++ b/app/models/concerns/enums/ci/pipeline.rb
@@ -15,7 +15,7 @@ module Enums
size_limit_exceeded: 21,
job_activity_limit_exceeded: 22,
deployments_limit_exceeded: 23,
- user_blocked: 24,
+ # 24 was previously used by the deprecated `user_blocked`
project_deleted: 25
}
end
diff --git a/app/models/concerns/file_store_mounter.rb b/app/models/concerns/file_store_mounter.rb
index bfcf8a1e7b9..f1ac734635d 100644
--- a/app/models/concerns/file_store_mounter.rb
+++ b/app/models/concerns/file_store_mounter.rb
@@ -4,9 +4,16 @@ module FileStoreMounter
extend ActiveSupport::Concern
class_methods do
- def mount_file_store_uploader(uploader)
+ # When `skip_store_file: true` is used, the model MUST explicitly call `store_file_now!`
+ def mount_file_store_uploader(uploader, skip_store_file: false)
mount_uploader(:file, uploader)
+ if skip_store_file
+ skip_callback :save, :after, :store_file!
+
+ return
+ end
+
# This hook is a no-op when the file is uploaded after_commit
after_save :update_file_store, if: :saved_change_to_file?
end
@@ -16,4 +23,9 @@ module FileStoreMounter
# The file.object_store is set during `uploader.store!` and `uploader.migrate!`
update_column(:file_store, file.object_store)
end
+
+ def store_file_now!
+ store_file!
+ update_file_store
+ end
end
diff --git a/app/models/concerns/integrations/base_data_fields.rb b/app/models/concerns/integrations/base_data_fields.rb
index 3cedb90756f..11bdd3aae7b 100644
--- a/app/models/concerns/integrations/base_data_fields.rb
+++ b/app/models/concerns/integrations/base_data_fields.rb
@@ -4,12 +4,15 @@ module Integrations
module BaseDataFields
extend ActiveSupport::Concern
+ LEGACY_FOREIGN_KEY_NAME = %w(
+ Integrations::IssueTrackerData
+ Integrations::JiraTrackerData
+ ).freeze
+
included do
# TODO: Once we rename the tables we can't rely on `table_name` anymore.
# https://gitlab.com/gitlab-org/gitlab/-/issues/331953
- belongs_to :integration, inverse_of: self.table_name.to_sym, foreign_key: :service_id
-
- delegate :activated?, to: :integration, allow_nil: true
+ belongs_to :integration, inverse_of: self.table_name.to_sym, foreign_key: foreign_key_name
validates :integration, presence: true
end
@@ -23,6 +26,26 @@ module Integrations
algorithm: 'aes-256-gcm'
}
end
+
+ private
+
+ # Older data field models use the `service_id` foreign key for the
+ # integration association.
+ def foreign_key_name
+ return :service_id if self.name.in?(LEGACY_FOREIGN_KEY_NAME)
+
+ :integration_id
+ end
+ end
+
+ def activated?
+ !!integration&.activated?
+ end
+
+ def to_database_hash
+ as_json(
+ only: self.class.column_names
+ ).except('id', 'service_id', 'integration_id', 'created_at', 'updated_at')
end
end
end
diff --git a/app/models/concerns/integrations/has_data_fields.rb b/app/models/concerns/integrations/has_data_fields.rb
index 25a1d855119..635147a2f3c 100644
--- a/app/models/concerns/integrations/has_data_fields.rb
+++ b/app/models/concerns/integrations/has_data_fields.rb
@@ -12,7 +12,8 @@ module Integrations
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
unless method_defined?(arg)
def #{arg}
- data_fields.send('#{arg}') || (properties && properties['#{arg}'])
+ value = data_fields.send('#{arg}')
+ value.nil? ? properties&.dig('#{arg}') : value
end
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 713a4386fee..4dca07132ef 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -106,23 +106,23 @@ module Issuable
scope :closed, -> { with_state(:closed) }
# rubocop:disable GitlabSecurity/SqlInjection
- # The `to_ability_name` method is not an user input.
+ # The `assignee_association_name` method is not an user input.
scope :assigned, -> do
- where("EXISTS (SELECT TRUE FROM #{to_ability_name}_assignees WHERE #{to_ability_name}_id = #{to_ability_name}s.id)")
+ where("EXISTS (SELECT TRUE FROM #{assignee_association_name}_assignees WHERE #{assignee_association_name}_id = #{assignee_association_name}s.id)")
end
scope :unassigned, -> do
- where("NOT EXISTS (SELECT TRUE FROM #{to_ability_name}_assignees WHERE #{to_ability_name}_id = #{to_ability_name}s.id)")
+ where("NOT EXISTS (SELECT TRUE FROM #{assignee_association_name}_assignees WHERE #{assignee_association_name}_id = #{assignee_association_name}s.id)")
end
scope :assigned_to, ->(users) do
- assignees_class = self.reflect_on_association("#{to_ability_name}_assignees").klass
+ assignees_class = self.reflect_on_association("#{assignee_association_name}_assignees").klass
- condition = assignees_class.where(user_id: users).where(Arel.sql("#{to_ability_name}_id = #{to_ability_name}s.id"))
+ condition = assignees_class.where(user_id: users).where(Arel.sql("#{assignee_association_name}_id = #{assignee_association_name}s.id"))
where(condition.arel.exists)
end
scope :not_assigned_to, ->(users) do
- assignees_class = self.reflect_on_association("#{to_ability_name}_assignees").klass
+ assignees_class = self.reflect_on_association("#{assignee_association_name}_assignees").klass
- condition = assignees_class.where(user_id: users).where(Arel.sql("#{to_ability_name}_id = #{to_ability_name}s.id"))
+ condition = assignees_class.where(user_id: users).where(Arel.sql("#{assignee_association_name}_id = #{assignee_association_name}s.id"))
where(condition.arel.exists.not)
end
# rubocop:enable GitlabSecurity/SqlInjection
@@ -195,8 +195,6 @@ module Issuable
end
def supports_escalation?
- return false unless ::Feature.enabled?(:incident_escalations, project)
-
incident?
end
@@ -414,6 +412,10 @@ module Issuable
def parent_class
::Project
end
+
+ def assignee_association_name
+ to_ability_name
+ end
end
def state
diff --git a/app/models/concerns/limitable.rb b/app/models/concerns/limitable.rb
index 6ff540b7866..0cccb7b51a8 100644
--- a/app/models/concerns/limitable.rb
+++ b/app/models/concerns/limitable.rb
@@ -15,17 +15,29 @@ module Limitable
validate :validate_plan_limit_not_exceeded, on: :create
end
+ def exceeds_limits?
+ limits, relation = fetch_plan_limit_data
+
+ limits&.exceeded?(limit_name, relation)
+ end
+
private
def validate_plan_limit_not_exceeded
+ limits, relation = fetch_plan_limit_data
+
+ check_plan_limit_not_exceeded(limits, relation)
+ end
+
+ def fetch_plan_limit_data
if GLOBAL_SCOPE == limit_scope
- validate_global_plan_limit_not_exceeded
+ global_plan_limits
else
- validate_scoped_plan_limit_not_exceeded
+ scoped_plan_limits
end
end
- def validate_scoped_plan_limit_not_exceeded
+ def scoped_plan_limits
scope_relation = self.public_send(limit_scope) # rubocop:disable GitlabSecurity/PublicSend
return unless scope_relation
return if limit_feature_flag && ::Feature.disabled?(limit_feature_flag, scope_relation)
@@ -34,18 +46,18 @@ module Limitable
relation = limit_relation ? self.public_send(limit_relation) : self.class.where(limit_scope => scope_relation) # rubocop:disable GitlabSecurity/PublicSend
limits = scope_relation.actual_limits
- check_plan_limit_not_exceeded(limits, relation)
+ [limits, relation]
end
- def validate_global_plan_limit_not_exceeded
+ def global_plan_limits
relation = self.class.all
limits = Plan.default.actual_limits
- check_plan_limit_not_exceeded(limits, relation)
+ [limits, relation]
end
def check_plan_limit_not_exceeded(limits, relation)
- return unless limits.exceeded?(limit_name, relation)
+ return unless limits&.exceeded?(limit_name, relation)
errors.add(:base, _("Maximum number of %{name} (%{count}) exceeded") %
{ name: limit_name.humanize(capitalize: false), count: limits.public_send(limit_name) }) # rubocop:disable GitlabSecurity/PublicSend
diff --git a/app/models/concerns/pg_full_text_searchable.rb b/app/models/concerns/pg_full_text_searchable.rb
index bfc539ee392..813827478da 100644
--- a/app/models/concerns/pg_full_text_searchable.rb
+++ b/app/models/concerns/pg_full_text_searchable.rb
@@ -24,6 +24,7 @@ module PgFullTextSearchable
LONG_WORDS_REGEX = %r([A-Za-z0-9+/@]{50,}).freeze
TSVECTOR_MAX_LENGTH = 1.megabyte.freeze
TEXT_SEARCH_DICTIONARY = 'english'
+ URL_SCHEME_REGEX = %r{(?<=\A|\W)\w+://(?=\w+)}.freeze
def update_search_data!
tsvector_sql_nodes = self.class.pg_full_text_searchable_columns.map do |column, weight|
@@ -104,6 +105,10 @@ module PgFullTextSearchable
def pg_full_text_search(search_term)
search_data_table = reflect_on_association(:search_data).klass.arel_table
+ # This fixes an inconsistency with how to_tsvector and websearch_to_tsquery process URLs
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/354784#note_905431920
+ search_term = remove_url_scheme(search_term)
+
joins(:search_data).where(
Arel::Nodes::InfixOperation.new(
'@@',
@@ -115,5 +120,11 @@ module PgFullTextSearchable
)
)
end
+
+ private
+
+ def remove_url_scheme(search_term)
+ search_term.gsub(URL_SCHEME_REGEX, '')
+ end
end
end
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index 0cab874a240..900e8f7d39b 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -66,6 +66,10 @@ module ProjectFeaturesCompatibility
write_feature_attribute_string(:snippets_access_level, value)
end
+ def package_registry_access_level=(value)
+ write_feature_attribute_string(:package_registry_access_level, value)
+ end
+
def pages_access_level=(value)
write_feature_attribute_string(:pages_access_level, value)
end
diff --git a/app/models/concerns/sensitive_serializable_hash.rb b/app/models/concerns/sensitive_serializable_hash.rb
index 94451fcd2c2..4ad8d16fcb9 100644
--- a/app/models/concerns/sensitive_serializable_hash.rb
+++ b/app/models/concerns/sensitive_serializable_hash.rb
@@ -10,7 +10,7 @@ module SensitiveSerializableHash
class_methods do
def prevent_from_serialization(*keys)
self.attributes_exempt_from_serializable_hash ||= []
- self.attributes_exempt_from_serializable_hash.concat keys
+ self.attributes_exempt_from_serializable_hash += keys
end
end
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index 948190dfadf..e418842a30b 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -23,22 +23,8 @@ module Storage
former_parent_full_path = parent_was&.full_path
parent_full_path = parent&.full_path
Gitlab::UploadsTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
-
- if any_project_with_pages_deployed?
- run_after_commit do
- Gitlab::PagesTransfer.new.async.move_namespace(path, former_parent_full_path, parent_full_path)
- end
- end
else
Gitlab::UploadsTransfer.new.rename_namespace(full_path_before_last_save, full_path)
-
- if any_project_with_pages_deployed?
- full_path_was = full_path_before_last_save
-
- run_after_commit do
- Gitlab::PagesTransfer.new.async.rename_namespace(full_path_was, full_path)
- end
- end
end
# If repositories moved successfully we need to