diff options
Diffstat (limited to 'app/models/concerns')
-rw-r--r-- | app/models/concerns/boards/listable.rb | 52 | ||||
-rw-r--r-- | app/models/concerns/bulk_insert_safe.rb | 4 | ||||
-rw-r--r-- | app/models/concerns/ci/artifactable.rb | 3 | ||||
-rw-r--r-- | app/models/concerns/each_batch.rb | 16 | ||||
-rw-r--r-- | app/models/concerns/enums/ci/commit_status.rb | 36 | ||||
-rw-r--r-- | app/models/concerns/enums/commit_status.rb | 35 | ||||
-rw-r--r-- | app/models/concerns/enums/vulnerability.rb | 46 | ||||
-rw-r--r-- | app/models/concerns/issuable.rb | 11 | ||||
-rw-r--r-- | app/models/concerns/issue_available_features.rb | 5 | ||||
-rw-r--r-- | app/models/concerns/milestoneable.rb | 2 | ||||
-rw-r--r-- | app/models/concerns/noteable.rb | 21 | ||||
-rw-r--r-- | app/models/concerns/packages/debian/architecture.rb | 25 | ||||
-rw-r--r-- | app/models/concerns/packages/debian/distribution.rb | 115 | ||||
-rw-r--r-- | app/models/concerns/repositories/can_housekeep_repository.rb | 25 |
14 files changed, 350 insertions, 46 deletions
diff --git a/app/models/concerns/boards/listable.rb b/app/models/concerns/boards/listable.rb new file mode 100644 index 00000000000..b7c0a8b3489 --- /dev/null +++ b/app/models/concerns/boards/listable.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Boards + module Listable + extend ActiveSupport::Concern + + included do + validates :label, :position, presence: true, if: :label? + validates :position, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :movable? + + before_destroy :can_be_destroyed + + scope :ordered, -> { order(:list_type, :position) } + scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) } + scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) } + end + + class_methods do + def destroyable_types + [:label] + end + + def movable_types + [:label] + end + end + + def destroyable? + self.class.destroyable_types.include?(list_type&.to_sym) + end + + def movable? + self.class.movable_types.include?(list_type&.to_sym) + end + + def title + if label? + label.name + elsif backlog? + _('Open') + else + list_type.humanize + end + end + + private + + def can_be_destroyed + throw(:abort) unless destroyable? # rubocop:disable Cop/BanCatchThrow + end + end +end diff --git a/app/models/concerns/bulk_insert_safe.rb b/app/models/concerns/bulk_insert_safe.rb index f9eb3fb875e..3748e77e933 100644 --- a/app/models/concerns/bulk_insert_safe.rb +++ b/app/models/concerns/bulk_insert_safe.rb @@ -53,9 +53,9 @@ module BulkInsertSafe class_methods do def set_callback(name, *args) unless _bulk_insert_callback_allowed?(name, args) - raise MethodNotAllowedError.new( + raise MethodNotAllowedError, "Not allowed to call `set_callback(#{name}, #{args})` when model extends `BulkInsertSafe`." \ - "Callbacks that fire per each record being inserted do not work with bulk-inserts.") + "Callbacks that fire per each record being inserted do not work with bulk-inserts." end super diff --git a/app/models/concerns/ci/artifactable.rb b/app/models/concerns/ci/artifactable.rb index 24df86dbc3c..cbe7d3b6abb 100644 --- a/app/models/concerns/ci/artifactable.rb +++ b/app/models/concerns/ci/artifactable.rb @@ -18,7 +18,8 @@ module Ci gzip: 3 }, _suffix: true - scope :expired, -> (limit) { where('expire_at < ?', Time.current).limit(limit) } + scope :expired_before, -> (timestamp) { where(arel_table[:expire_at].lt(timestamp)) } + scope :expired, -> (limit) { expired_before(Time.current).limit(limit) } end def each_blob(&blk) diff --git a/app/models/concerns/each_batch.rb b/app/models/concerns/each_batch.rb index af5f4e30d06..a59f00d73ec 100644 --- a/app/models/concerns/each_batch.rb +++ b/app/models/concerns/each_batch.rb @@ -47,7 +47,7 @@ module EachBatch # order_hint does not affect the search results. For example, # `ORDER BY id ASC, updated_at ASC` means the same thing as `ORDER # BY id ASC`. - def each_batch(of: 1000, column: primary_key, order_hint: nil) + def each_batch(of: 1000, column: primary_key, order: :asc, order_hint: nil) unless column raise ArgumentError, 'the column: argument must be set to a column name to use for ordering rows' @@ -55,7 +55,7 @@ module EachBatch start = except(:select) .select(column) - .reorder(column => :asc) + .reorder(column => order) start = start.order(order_hint) if order_hint start = start.take @@ -66,10 +66,12 @@ module EachBatch arel_table = self.arel_table 1.step do |index| + start_cond = arel_table[column].gteq(start_id) + start_cond = arel_table[column].lteq(start_id) if order == :desc stop = except(:select) .select(column) - .where(arel_table[column].gteq(start_id)) - .reorder(column => :asc) + .where(start_cond) + .reorder(column => order) stop = stop.order(order_hint) if order_hint stop = stop @@ -77,12 +79,14 @@ module EachBatch .limit(1) .take - relation = where(arel_table[column].gteq(start_id)) + relation = where(start_cond) if stop stop_id = stop[column] start_id = stop_id - relation = relation.where(arel_table[column].lt(stop_id)) + stop_cond = arel_table[column].lt(stop_id) + stop_cond = arel_table[column].gt(stop_id) if order == :desc + relation = relation.where(stop_cond) end # Any ORDER BYs are useless for this relation and can lead to less diff --git a/app/models/concerns/enums/ci/commit_status.rb b/app/models/concerns/enums/ci/commit_status.rb new file mode 100644 index 00000000000..48b4a402974 --- /dev/null +++ b/app/models/concerns/enums/ci/commit_status.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true +module Enums + module Ci + module CommitStatus + # Returns the Hash to use for creating the `failure_reason` enum for + # `CommitStatus`. + def self.failure_reasons + { + unknown_failure: nil, + script_failure: 1, + api_failure: 2, + stuck_or_timeout_failure: 3, + runner_system_failure: 4, + missing_dependency_failure: 5, + runner_unsupported: 6, + stale_schedule: 7, + job_execution_timeout: 8, + archived_failure: 9, + unmet_prerequisites: 10, + scheduler_failure: 11, + data_integrity_failure: 12, + forward_deployment_failure: 13, + insufficient_bridge_permissions: 1_001, + downstream_bridge_project_not_found: 1_002, + invalid_bridge_trigger: 1_003, + bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data + downstream_pipeline_creation_failed: 1_007, + secrets_provider_not_found: 1_008, + reached_max_descendant_pipelines_depth: 1_009 + } + end + end + end +end + +Enums::Ci::CommitStatus.prepend_if_ee('EE::Enums::Ci::CommitStatus') diff --git a/app/models/concerns/enums/commit_status.rb b/app/models/concerns/enums/commit_status.rb deleted file mode 100644 index faeed7276ab..00000000000 --- a/app/models/concerns/enums/commit_status.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module Enums - module CommitStatus - # Returns the Hash to use for creating the `failure_reason` enum for - # `CommitStatus`. - def self.failure_reasons - { - unknown_failure: nil, - script_failure: 1, - api_failure: 2, - stuck_or_timeout_failure: 3, - runner_system_failure: 4, - missing_dependency_failure: 5, - runner_unsupported: 6, - stale_schedule: 7, - job_execution_timeout: 8, - archived_failure: 9, - unmet_prerequisites: 10, - scheduler_failure: 11, - data_integrity_failure: 12, - forward_deployment_failure: 13, - insufficient_bridge_permissions: 1_001, - downstream_bridge_project_not_found: 1_002, - invalid_bridge_trigger: 1_003, - bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data - downstream_pipeline_creation_failed: 1_007, - secrets_provider_not_found: 1_008, - reached_max_descendant_pipelines_depth: 1_009 - } - end - end -end - -Enums::CommitStatus.prepend_if_ee('EE::Enums::CommitStatus') diff --git a/app/models/concerns/enums/vulnerability.rb b/app/models/concerns/enums/vulnerability.rb new file mode 100644 index 00000000000..4b2e9e9e0b2 --- /dev/null +++ b/app/models/concerns/enums/vulnerability.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Enums + module Vulnerability + CONFIDENCE_LEVELS = { + # undefined: 0, no longer applicable + ignore: 1, + unknown: 2, + experimental: 3, + low: 4, + medium: 5, + high: 6, + confirmed: 7 + }.with_indifferent_access.freeze + + REPORT_TYPES = { + sast: 0, + secret_detection: 4 + }.with_indifferent_access.freeze + + SEVERITY_LEVELS = { + # undefined: 0, no longer applicable + info: 1, + unknown: 2, + # experimental: 3, formerly used by confidence, no longer applicable + low: 4, + medium: 5, + high: 6, + critical: 7 + }.with_indifferent_access.freeze + + def self.confidence_levels + CONFIDENCE_LEVELS + end + + def self.report_types + REPORT_TYPES + end + + def self.severity_levels + SEVERITY_LEVELS + end + end +end + +Enums::Vulnerability.prepend_if_ee('EE::Enums::Vulnerability') diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index c3a394c1ca5..83ff5b16efe 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -196,6 +196,10 @@ module Issuable is_a?(Issue) end + def supports_assignee? + false + end + def severity return IssuableSeverity::DEFAULT unless supports_severity? @@ -216,6 +220,10 @@ module Issuable end class_methods do + def participant_includes + [:assignees, :author, { notes: [:author, :award_emoji] }] + end + # Searches for records with a matching title. # # This method uses ILIKE on PostgreSQL. @@ -344,12 +352,15 @@ module Issuable # # Returns an array of arel columns def grouping_columns(sort) + sort = sort.to_s grouping_columns = [arel_table[:id]] if %w(milestone_due_desc milestone_due_asc milestone).include?(sort) milestone_table = Milestone.arel_table grouping_columns << milestone_table[:id] grouping_columns << milestone_table[:due_date] + elsif %w(merged_at_desc merged_at_asc).include?(sort) + grouping_columns << MergeRequest::Metrics.arel_table[:merged_at] end grouping_columns diff --git a/app/models/concerns/issue_available_features.rb b/app/models/concerns/issue_available_features.rb index 886db133a94..a5ffa959174 100644 --- a/app/models/concerns/issue_available_features.rb +++ b/app/models/concerns/issue_available_features.rb @@ -9,7 +9,10 @@ module IssueAvailableFeatures class_methods do # EE only features are listed on EE::IssueAvailableFeatures def available_features_for_issue_types - {}.with_indifferent_access + { + assignee: %w(issue incident), + confidentiality: %(issue incident) + }.with_indifferent_access end end diff --git a/app/models/concerns/milestoneable.rb b/app/models/concerns/milestoneable.rb index b1698bc2ee3..ccb334343ff 100644 --- a/app/models/concerns/milestoneable.rb +++ b/app/models/concerns/milestoneable.rb @@ -51,7 +51,7 @@ module Milestoneable # Overridden on EE module # def supports_milestone? - respond_to?(:milestone_id) && !incident? + respond_to?(:milestone_id) end end diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb index 2dbe9360d42..f3cc68e4b85 100644 --- a/app/models/concerns/noteable.rb +++ b/app/models/concerns/noteable.rb @@ -19,6 +19,11 @@ module Noteable def resolvable_types %w(MergeRequest DesignManagement::Design) end + + # `Noteable` class names that support creating/forwarding individual notes. + def email_creatable_types + %w(Issue) + end end # The timestamp of the note (e.g. the :created_at or :updated_at attribute if provided via @@ -55,6 +60,10 @@ module Noteable supports_discussions? && self.class.replyable_types.include?(base_class_name) end + def supports_creating_notes_by_email? + self.class.email_creatable_types.include?(base_class_name) + end + def supports_suggestion? false end @@ -158,6 +167,18 @@ module Noteable def after_note_destroyed(_note) # no-op end + + # Email address that an authorized user can send/forward an email to be added directly + # to an issue or merge request. + # example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-issue-34@localhost.com + def creatable_note_email_address(author) + return unless supports_creating_notes_by_email? + + project_email = project.new_issuable_address(author, self.class.name.underscore) + return unless project_email + + project_email.sub('@', "-#{iid}@") + end end Noteable.extend(Noteable::ClassMethods) diff --git a/app/models/concerns/packages/debian/architecture.rb b/app/models/concerns/packages/debian/architecture.rb new file mode 100644 index 00000000000..4aa633e0357 --- /dev/null +++ b/app/models/concerns/packages/debian/architecture.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Packages + module Debian + module Architecture + extend ActiveSupport::Concern + + included do + belongs_to :distribution, class_name: "Packages::Debian::#{container_type.capitalize}Distribution", inverse_of: :architectures + + validates :distribution, + presence: true + + validates :name, + presence: true, + length: { maximum: 255 }, + uniqueness: { scope: %i[distribution_id] }, + format: { with: Gitlab::Regex.debian_architecture_regex } + + scope :with_distribution, ->(distribution) { where(distribution: distribution) } + scope :with_name, ->(name) { where(name: name) } + end + end + end +end diff --git a/app/models/concerns/packages/debian/distribution.rb b/app/models/concerns/packages/debian/distribution.rb new file mode 100644 index 00000000000..285d293c9ee --- /dev/null +++ b/app/models/concerns/packages/debian/distribution.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module Packages + module Debian + module Distribution + extend ActiveSupport::Concern + + included do + include FileStoreMounter + + def self.container_foreign_key + "#{container_type}_id".to_sym + end + + alias_attribute :container, container_type + alias_attribute :container_id, "#{container_type}_id" + + belongs_to container_type + belongs_to :creator, class_name: 'User' + + has_many :architectures, + class_name: "Packages::Debian::#{container_type.capitalize}Architecture", + foreign_key: :distribution_id, + inverse_of: :distribution + + validates :codename, + presence: true, + uniqueness: { scope: [container_foreign_key] }, + format: { with: Gitlab::Regex.debian_distribution_regex } + + validates :suite, + allow_nil: true, + format: { with: Gitlab::Regex.debian_distribution_regex } + validates :suite, + uniqueness: { scope: [container_foreign_key] }, + if: :suite + + validate :unique_codename_and_suite + + validates :origin, + allow_nil: true, + format: { with: Gitlab::Regex.debian_distribution_regex } + + validates :label, + allow_nil: true, + format: { with: Gitlab::Regex.debian_distribution_regex } + + validates :version, + allow_nil: true, + format: { with: Gitlab::Regex.debian_version_regex } + + # The Valid-Until field is a security measure to prevent malicious attackers to + # serve an outdated repository, with vulnerable packages + # (keeping in mind that most Debian repository are not using TLS but use GPG + # signatures instead). + # A minimum of 24 hours is simply to avoid generating indices too often + # (which generates load). + # Official Debian repositories are generated 4 times a day, and valid for 7 days. + # Full ref: https://wiki.debian.org/DebianRepository/Format#Date.2C_Valid-Until + validates :valid_time_duration_seconds, + allow_nil: true, + numericality: { greater_than_or_equal_to: 24.hours.to_i } + + validates container_type, presence: true + validates :file_store, presence: true + + validates :file_signature, absence: true + validates :signing_keys, absence: true + + scope :with_container, ->(subject) { where(container_type => subject) } + scope :with_codename, ->(codename) { where(codename: codename) } + scope :with_suite, ->(suite) { where(suite: suite) } + scope :with_codename_or_suite, ->(codename_or_suite) { with_codename(codename_or_suite).or(with_suite(codename_or_suite)) } + + attr_encrypted :signing_keys, + mode: :per_attribute_iv, + key: Settings.attr_encrypted_db_key_base_truncated, + algorithm: 'aes-256-gcm', + encode: false, + encode_iv: false + + mount_file_store_uploader Packages::Debian::DistributionReleaseFileUploader + + def needs_update? + !file.exists? || time_duration_expired? + end + + private + + def time_duration_expired? + return false unless valid_time_duration_seconds.present? + + updated_at + valid_time_duration_seconds.seconds + 6.hours < Time.current + end + + def unique_codename_and_suite + errors.add(:codename, _('has already been taken as Suite')) if codename_exists_as_suite? + errors.add(:suite, _('has already been taken as Codename')) if suite_exists_as_codename? + end + + def codename_exists_as_suite? + return false unless codename.present? + + self.class.with_container(container).with_suite(codename).exists? + end + + def suite_exists_as_codename? + return false unless suite.present? + + self.class.with_container(container).with_codename(suite).exists? + end + end + end + end +end diff --git a/app/models/concerns/repositories/can_housekeep_repository.rb b/app/models/concerns/repositories/can_housekeep_repository.rb new file mode 100644 index 00000000000..2b79851a07c --- /dev/null +++ b/app/models/concerns/repositories/can_housekeep_repository.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Repositories + module CanHousekeepRepository + extend ActiveSupport::Concern + + def pushes_since_gc + Gitlab::Redis::SharedState.with { |redis| redis.get(pushes_since_gc_redis_shared_state_key).to_i } + end + + def increment_pushes_since_gc + Gitlab::Redis::SharedState.with { |redis| redis.incr(pushes_since_gc_redis_shared_state_key) } + end + + def reset_pushes_since_gc + Gitlab::Redis::SharedState.with { |redis| redis.del(pushes_since_gc_redis_shared_state_key) } + end + + private + + def pushes_since_gc_redis_shared_state_key + "#{self.class.name.underscore.pluralize}/#{id}/pushes_since_gc" + end + end +end |