summaryrefslogtreecommitdiff
path: root/app/models/concerns
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-11-18 13:16:36 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-11-18 13:16:36 +0000
commit311b0269b4eb9839fa63f80c8d7a58f32b8138a0 (patch)
tree07e7870bca8aed6d61fdcc810731c50d2c40af47 /app/models/concerns
parent27909cef6c4170ed9205afa7426b8d3de47cbb0c (diff)
downloadgitlab-ce-311b0269b4eb9839fa63f80c8d7a58f32b8138a0.tar.gz
Add latest changes from gitlab-org/gitlab@14-5-stable-eev14.5.0-rc42
Diffstat (limited to 'app/models/concerns')
-rw-r--r--app/models/concerns/alert_event_lifecycle.rb2
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stage_event_model.rb28
-rw-r--r--app/models/concerns/cascading_namespace_setting_attribute.rb6
-rw-r--r--app/models/concerns/ci/contextable.rb33
-rw-r--r--app/models/concerns/ci/has_status.rb17
-rw-r--r--app/models/concerns/ci/metadatable.rb3
-rw-r--r--app/models/concerns/clusters/agents/authorization_config_scopes.rb25
-rw-r--r--app/models/concerns/database_reflection.rb21
-rw-r--r--app/models/concerns/enums/vulnerability.rb13
-rw-r--r--app/models/concerns/file_store_mounter.rb8
-rw-r--r--app/models/concerns/has_integrations.rb19
-rw-r--r--app/models/concerns/has_user_type.rb1
-rw-r--r--app/models/concerns/integrations/push_data_validations.rb44
-rw-r--r--app/models/concerns/integrations/reactively_cached.rb15
-rw-r--r--app/models/concerns/issuable.rb1
-rw-r--r--app/models/concerns/legacy_bulk_insert.rb54
-rw-r--r--app/models/concerns/loaded_in_group_list.rb2
-rw-r--r--app/models/concerns/loose_foreign_key.rb22
-rw-r--r--app/models/concerns/merge_request_reviewer_state.rb25
-rw-r--r--app/models/concerns/milestoneable.rb3
-rw-r--r--app/models/concerns/noteable.rb33
-rw-r--r--app/models/concerns/reactive_service.rb13
-rw-r--r--app/models/concerns/security/latest_pipeline_information.rb36
-rw-r--r--app/models/concerns/service_push_data_validations.rb43
-rw-r--r--app/models/concerns/sha256_attribute.rb2
-rw-r--r--app/models/concerns/sha_attribute.rb2
-rw-r--r--app/models/concerns/strip_attribute.rb3
-rw-r--r--app/models/concerns/timebox.rb16
-rw-r--r--app/models/concerns/transactions.rb28
-rw-r--r--app/models/concerns/ttl_expirable.rb7
-rw-r--r--app/models/concerns/update_highest_role.rb2
-rw-r--r--app/models/concerns/x509_serial_number_attribute.rb2
32 files changed, 387 insertions, 142 deletions
diff --git a/app/models/concerns/alert_event_lifecycle.rb b/app/models/concerns/alert_event_lifecycle.rb
index 4d2b717ead2..72fe7757b44 100644
--- a/app/models/concerns/alert_event_lifecycle.rb
+++ b/app/models/concerns/alert_event_lifecycle.rb
@@ -41,8 +41,6 @@ module AlertEventLifecycle
scope :firing, -> { where(status: status_value_for(:firing)) }
scope :resolved, -> { where(status: status_value_for(:resolved)) }
- scope :count_by_project_id, -> { group(:project_id).count }
-
def self.status_value_for(name)
state_machines[:status].states[name].value
end
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 7462e1e828b..324e0fb57cb 100644
--- a/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
+++ b/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
@@ -5,6 +5,23 @@ module Analytics
module StageEventModel
extend ActiveSupport::Concern
+ included do
+ scope :by_stage_event_hash_id, ->(id) { where(stage_event_hash_id: id) }
+ scope :by_project_id, ->(id) { where(project_id: id) }
+ scope :by_group_id, ->(id) { where(group_id: id) }
+ scope :end_event_timestamp_after, -> (date) { where(arel_table[:end_event_timestamp].gteq(date)) }
+ scope :end_event_timestamp_before, -> (date) { where(arel_table[:end_event_timestamp].lteq(date)) }
+ scope :start_event_timestamp_after, -> (date) { where(arel_table[:start_event_timestamp].gteq(date)) }
+ scope :start_event_timestamp_before, -> (date) { where(arel_table[:start_event_timestamp].lteq(date)) }
+ scope :authored, ->(user) { where(author_id: user) }
+ scope :with_milestone_id, ->(milestone_id) { where(milestone_id: milestone_id) }
+ scope :end_event_is_not_happened_yet, -> { where(end_event_timestamp: nil) }
+ end
+
+ def issuable_id
+ attributes[self.class.issuable_id_column.to_s]
+ end
+
class_methods do
def upsert_data(data)
upsert_values = data.map do |row|
@@ -13,8 +30,9 @@ module Analytics
:issuable_id,
:group_id,
:project_id,
- :author_id,
:milestone_id,
+ :author_id,
+ :state_id,
:start_event_timestamp,
:end_event_timestamp
)
@@ -31,6 +49,7 @@ module Analytics
project_id,
milestone_id,
author_id,
+ state_id,
start_event_timestamp,
end_event_timestamp
)
@@ -39,10 +58,11 @@ module Analytics
DO UPDATE SET
group_id = excluded.group_id,
project_id = excluded.project_id,
- start_event_timestamp = excluded.start_event_timestamp,
- end_event_timestamp = excluded.end_event_timestamp,
milestone_id = excluded.milestone_id,
- author_id = excluded.author_id
+ author_id = excluded.author_id,
+ state_id = excluded.state_id,
+ start_event_timestamp = excluded.start_event_timestamp,
+ end_event_timestamp = excluded.end_event_timestamp
SQL
result = connection.execute(query)
diff --git a/app/models/concerns/cascading_namespace_setting_attribute.rb b/app/models/concerns/cascading_namespace_setting_attribute.rb
index e58e5ddc966..731729a1ed5 100644
--- a/app/models/concerns/cascading_namespace_setting_attribute.rb
+++ b/app/models/concerns/cascading_namespace_setting_attribute.rb
@@ -127,7 +127,7 @@ module CascadingNamespaceSettingAttribute
end
def alias_boolean(attribute)
- return unless Gitlab::Database.main.exists? && type_for_attribute(attribute).type == :boolean
+ return unless database.exists? && type_for_attribute(attribute).type == :boolean
alias_method :"#{attribute}?", attribute
end
@@ -176,10 +176,10 @@ module CascadingNamespaceSettingAttribute
private
def locked_value(attribute)
+ return application_setting_value(attribute) if locked_by_application_setting?(attribute)
+
ancestor = locked_ancestor(attribute)
return ancestor.read_attribute(attribute) if ancestor
-
- Gitlab::CurrentSettings.public_send(attribute) # rubocop:disable GitlabSecurity/PublicSend
end
def locked_ancestor(attribute)
diff --git a/app/models/concerns/ci/contextable.rb b/app/models/concerns/ci/contextable.rb
index 27a704c1de0..a9589cea5e9 100644
--- a/app/models/concerns/ci/contextable.rb
+++ b/app/models/concerns/ci/contextable.rb
@@ -10,11 +10,14 @@ module Ci
# Variables in the environment name scope.
#
def scoped_variables(environment: expanded_environment_name, dependencies: true)
- Gitlab::Ci::Variables::Collection.new.tap do |variables|
- variables.concat(predefined_variables)
+ track_duration do
+ variables = pipeline.variables_builder.scoped_variables(self, environment: environment, dependencies: dependencies)
+
+ variables.concat(predefined_variables) unless pipeline.predefined_vars_in_builder_enabled?
variables.concat(project.predefined_variables)
variables.concat(pipeline.predefined_variables)
variables.concat(runner.predefined_variables) if runnable? && runner
+ variables.concat(kubernetes_variables)
variables.concat(deployment_variables(environment: environment))
variables.concat(yaml_variables)
variables.concat(user_variables)
@@ -25,9 +28,23 @@ module Ci
variables.concat(trigger_request.user_variables) if trigger_request
variables.concat(pipeline.variables)
variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
+
+ variables
end
end
+ def track_duration
+ start_time = ::Gitlab::Metrics::System.monotonic_time
+ result = yield
+ duration = ::Gitlab::Metrics::System.monotonic_time - start_time
+
+ ::Gitlab::Ci::Pipeline::Metrics
+ .pipeline_builder_scoped_variables_histogram
+ .observe({}, duration.seconds)
+
+ result
+ end
+
##
# Variables that do not depend on the environment name.
#
@@ -72,6 +89,18 @@ module Ci
end
end
+ def kubernetes_variables
+ ::Gitlab::Ci::Variables::Collection.new.tap do |collection|
+ # Should get merged with the cluster kubeconfig in deployment_variables, see
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/335089
+ template = ::Ci::GenerateKubeconfigService.new(self).execute
+
+ if template.valid?
+ collection.append(key: 'KUBECONFIG', value: template.to_yaml, public: false, file: true)
+ end
+ end
+ end
+
def deployment_variables(environment:)
return [] unless environment
diff --git a/app/models/concerns/ci/has_status.rb b/app/models/concerns/ci/has_status.rb
index 8d715279da8..ccaccec3b6b 100644
--- a/app/models/concerns/ci/has_status.rb
+++ b/app/models/concerns/ci/has_status.rb
@@ -14,21 +14,8 @@ module Ci
PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze
EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze
STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
- failed: 4, canceled: 5, skipped: 6, manual: 7,
- scheduled: 8, preparing: 9, waiting_for_resource: 10 }.freeze
- STATUSES_DESCRIPTION = {
- created: 'Pipeline has been created',
- waiting_for_resource: 'A resource (for example, a runner) that the pipeline requires to run is unavailable',
- preparing: 'Pipeline is preparing to run',
- pending: 'Pipeline has not started running yet',
- running: 'Pipeline is running',
- failed: 'At least one stage of the pipeline failed',
- success: 'Pipeline completed successfully',
- canceled: 'Pipeline was canceled before completion',
- skipped: 'Pipeline was skipped',
- manual: 'Pipeline needs to be manually started',
- scheduled: 'Pipeline is scheduled to run'
- }.freeze
+ failed: 4, canceled: 5, skipped: 6, manual: 7,
+ scheduled: 8, preparing: 9, waiting_for_resource: 10 }.freeze
UnknownStatusError = Class.new(StandardError)
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index 344f5aa4cd5..611b27c722b 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -20,7 +20,8 @@ module Ci
delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
delegate :has_exposed_artifacts?, to: :metadata, prefix: false, allow_nil: true
delegate :environment_auto_stop_in, to: :metadata, prefix: false, allow_nil: true
- delegate :runner_features, to: :metadata, prefix: false, allow_nil: false
+ delegate :set_cancel_gracefully, to: :metadata, prefix: false, allow_nil: false
+ delegate :cancel_gracefully?, to: :metadata, prefix: false, allow_nil: false
before_create :ensure_metadata
end
diff --git a/app/models/concerns/clusters/agents/authorization_config_scopes.rb b/app/models/concerns/clusters/agents/authorization_config_scopes.rb
new file mode 100644
index 00000000000..0a0406c3389
--- /dev/null
+++ b/app/models/concerns/clusters/agents/authorization_config_scopes.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Agents
+ module AuthorizationConfigScopes
+ extend ActiveSupport::Concern
+
+ included do
+ scope :with_available_ci_access_fields, ->(project) {
+ where("config->'access_as' IS NULL")
+ .or(where("config->'access_as' = '{}'"))
+ .or(where("config->'access_as' ?| array[:fields]", fields: available_ci_access_fields(project)))
+ }
+ end
+
+ class_methods do
+ def available_ci_access_fields(_project)
+ %w(agent)
+ end
+ end
+ end
+ end
+end
+
+Clusters::Agents::AuthorizationConfigScopes.prepend_mod
diff --git a/app/models/concerns/database_reflection.rb b/app/models/concerns/database_reflection.rb
new file mode 100644
index 00000000000..1842f5bf4ec
--- /dev/null
+++ b/app/models/concerns/database_reflection.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# A module that makes it easier/less verbose to reflect upon a database
+# connection.
+#
+# Using this module you can write this:
+#
+# User.database.database_name
+#
+# Instead of this:
+#
+# Gitlab::Database::Reflection.new(User).database_name
+module DatabaseReflection
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def database
+ @database_reflection ||= ::Gitlab::Database::Reflection.new(self)
+ end
+ end
+end
diff --git a/app/models/concerns/enums/vulnerability.rb b/app/models/concerns/enums/vulnerability.rb
index 749d1ad65cd..4b325de61bc 100644
--- a/app/models/concerns/enums/vulnerability.rb
+++ b/app/models/concerns/enums/vulnerability.rb
@@ -37,6 +37,15 @@ module Enums
security_audit: 4
}.with_indifferent_access.freeze
+ # keep the order of the values in the state enum, it is used in state_order method to properly order vulnerabilities based on state
+ # remember to recreate index_vulnerabilities_on_state_case_id index when you update or extend this enum
+ VULNERABILITY_STATES = {
+ detected: 1,
+ confirmed: 4,
+ resolved: 3,
+ dismissed: 2
+ }.with_indifferent_access.freeze
+
def self.confidence_levels
CONFIDENCE_LEVELS
end
@@ -52,6 +61,10 @@ module Enums
def self.detection_methods
DETECTION_METHODS
end
+
+ def self.vulnerability_states
+ VULNERABILITY_STATES
+ end
end
end
diff --git a/app/models/concerns/file_store_mounter.rb b/app/models/concerns/file_store_mounter.rb
index 9d4463e5297..bfcf8a1e7b9 100644
--- a/app/models/concerns/file_store_mounter.rb
+++ b/app/models/concerns/file_store_mounter.rb
@@ -7,15 +7,13 @@ module FileStoreMounter
def mount_file_store_uploader(uploader)
mount_uploader(:file, uploader)
+ # This hook is a no-op when the file is uploaded after_commit
after_save :update_file_store, if: :saved_change_to_file?
end
end
- private
-
def update_file_store
- # The file.object_store is set during `uploader.store!`
- # which happens after object is inserted/updated
- self.update_column(:file_store, file.object_store)
+ # The file.object_store is set during `uploader.store!` and `uploader.migrate!`
+ update_column(:file_store, file.object_store)
end
end
diff --git a/app/models/concerns/has_integrations.rb b/app/models/concerns/has_integrations.rb
deleted file mode 100644
index 76e03d68600..00000000000
--- a/app/models/concerns/has_integrations.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-module HasIntegrations
- extend ActiveSupport::Concern
-
- class_methods do
- def without_integration(integration)
- integrations = Integration
- .select('1')
- .where("#{Integration.table_name}.project_id = projects.id")
- .where(type: integration.type)
-
- Project
- .where('NOT EXISTS (?)', integrations)
- .where(pending_delete: false)
- .where(archived: false)
- end
- end
-end
diff --git a/app/models/concerns/has_user_type.rb b/app/models/concerns/has_user_type.rb
index 4b4f9c0df84..28ee54afaa9 100644
--- a/app/models/concerns/has_user_type.rb
+++ b/app/models/concerns/has_user_type.rb
@@ -28,6 +28,7 @@ module HasUserType
scope :non_internal, -> { humans.or(where(user_type: NON_INTERNAL_USER_TYPES)) }
scope :without_ghosts, -> { humans.or(where.not(user_type: :ghost)) }
scope :without_project_bot, -> { humans.or(where.not(user_type: :project_bot)) }
+ scope :human_or_service_user, -> { humans.or(where(user_type: :service_user)) }
enum user_type: USER_TYPES
diff --git a/app/models/concerns/integrations/push_data_validations.rb b/app/models/concerns/integrations/push_data_validations.rb
new file mode 100644
index 00000000000..966fc94e289
--- /dev/null
+++ b/app/models/concerns/integrations/push_data_validations.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+# This concern is used by registered integrations such as Integrations::TeamCity and
+# Integrations::DroneCi and adds methods to perform validations on the received
+# data.
+module Integrations
+ module PushDataValidations
+ extend ActiveSupport::Concern
+
+ def merge_request_valid?(data)
+ data.dig(:object_attributes, :state) == 'opened' && merge_request_unchecked?(data)
+ end
+
+ def push_valid?(data)
+ data[:total_commits_count] > 0 &&
+ !branch_removed?(data) &&
+ # prefer merge request trigger over push to avoid double builds
+ !opened_merge_requests?(data)
+ end
+
+ def tag_push_valid?(data)
+ data[:total_commits_count] > 0 && !branch_removed?(data)
+ end
+
+ private
+
+ def branch_removed?(data)
+ Gitlab::Git.blank_ref?(data[:after])
+ end
+
+ def opened_merge_requests?(data)
+ project.merge_requests
+ .opened
+ .from_project(project)
+ .from_source_branches(Gitlab::Git.ref_name(data[:ref]))
+ .exists?
+ end
+
+ def merge_request_unchecked?(data)
+ MergeRequest.state_machines[:merge_status]
+ .check_state?(data.dig(:object_attributes, :merge_status))
+ end
+ end
+end
diff --git a/app/models/concerns/integrations/reactively_cached.rb b/app/models/concerns/integrations/reactively_cached.rb
new file mode 100644
index 00000000000..62eff06c8e2
--- /dev/null
+++ b/app/models/concerns/integrations/reactively_cached.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Integrations
+ module ReactivelyCached
+ extend ActiveSupport::Concern
+
+ included do
+ include ::ReactiveCaching
+
+ # Default cache key: class name + project_id
+ self.reactive_cache_key = ->(integration) { [integration.class.model_name.singular, integration.project_id] }
+ self.reactive_cache_work_type = :external_dependency
+ end
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 5c307158a9a..4273eb331a1 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -92,7 +92,6 @@ module Issuable
scope :recent, -> { reorder(id: :desc) }
scope :of_projects, ->(ids) { where(project_id: ids) }
scope :opened, -> { with_state(:opened) }
- scope :only_opened, -> { with_state(:opened) }
scope :closed, -> { with_state(:closed) }
# rubocop:disable GitlabSecurity/SqlInjection
diff --git a/app/models/concerns/legacy_bulk_insert.rb b/app/models/concerns/legacy_bulk_insert.rb
new file mode 100644
index 00000000000..1249dfb70cd
--- /dev/null
+++ b/app/models/concerns/legacy_bulk_insert.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module LegacyBulkInsert
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Bulk inserts a number of rows into a table, optionally returning their
+ # IDs.
+ #
+ # This method is deprecated, and you should use the BulkInsertSafe module
+ # instead.
+ #
+ # table - The name of the table to insert the rows into.
+ # rows - An Array of Hash instances, each mapping the columns to their
+ # values.
+ # return_ids - When set to true the return value will be an Array of IDs of
+ # the inserted rows
+ # disable_quote - A key or an Array of keys to exclude from quoting (You
+ # become responsible for protection from SQL injection for
+ # these keys!)
+ # on_conflict - Defines an upsert. Values can be: :disabled (default) or
+ # :do_nothing
+ def legacy_bulk_insert(table, rows, return_ids: false, disable_quote: [], on_conflict: nil)
+ return if rows.empty?
+
+ keys = rows.first.keys
+ columns = keys.map { |key| connection.quote_column_name(key) }
+
+ disable_quote = Array(disable_quote).to_set
+ tuples = rows.map do |row|
+ keys.map do |k|
+ disable_quote.include?(k) ? row[k] : connection.quote(row[k])
+ end
+ end
+
+ sql = <<-EOF
+ INSERT INTO #{table} (#{columns.join(', ')})
+ VALUES #{tuples.map { |tuple| "(#{tuple.join(', ')})" }.join(', ')}
+ EOF
+
+ sql = "#{sql} ON CONFLICT DO NOTHING" if on_conflict == :do_nothing
+
+ sql = "#{sql} RETURNING id" if return_ids
+
+ result = connection.execute(sql)
+
+ if return_ids
+ result.values.map { |tuple| tuple[0].to_i }
+ else
+ []
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/loaded_in_group_list.rb b/app/models/concerns/loaded_in_group_list.rb
index 848ef63f1c2..98f6ad58434 100644
--- a/app/models/concerns/loaded_in_group_list.rb
+++ b/app/models/concerns/loaded_in_group_list.rb
@@ -41,9 +41,11 @@ module LoadedInGroupList
namespaces = Namespace.arel_table
children = namespaces.alias('children')
+ # TODO 6473: remove the filtering of the Namespaces::ProjectNamespace see https://gitlab.com/groups/gitlab-org/-/epics/6473
namespaces.project(Arel.star.count.as('preloaded_subgroup_count'))
.from(children)
.where(children[:parent_id].eq(namespaces[:id]))
+ .where(children[:type].is_distinct_from(Namespaces::ProjectNamespace.sti_name))
end
def member_count_sql
diff --git a/app/models/concerns/loose_foreign_key.rb b/app/models/concerns/loose_foreign_key.rb
index 4e822a04869..102292672b3 100644
--- a/app/models/concerns/loose_foreign_key.rb
+++ b/app/models/concerns/loose_foreign_key.rb
@@ -7,20 +7,18 @@ module LooseForeignKey
# Loose foreign keys allow delayed processing of associated database records
# with similar guarantees than a database foreign key.
#
- # TODO: finalize this later once the async job is in place
- #
# Prerequisites:
#
# To start using the concern, you'll need to install a database trigger to the parent
# table in a standard DB migration (not post-migration).
#
- # > add_loose_foreign_key_support(:projects, :gitlab_main)
+ # > track_record_deletions(:projects)
#
# Usage:
#
# > class Ci::Build < ApplicationRecord
# >
- # > loose_foreign_key :security_scans, :build_id, on_delete: :async_delete, gitlab_schema: :gitlab_main
+ # > loose_foreign_key :security_scans, :build_id, on_delete: :async_delete
# >
# > # associations can be still defined, the dependent options is no longer necessary:
# > has_many :security_scans, class_name: 'Security::Scan'
@@ -32,14 +30,6 @@ module LooseForeignKey
# - :async_delete - deletes the children rows via an asynchronous process.
# - :async_nullify - sets the foreign key column to null via an asynchronous process.
#
- # Options for gitlab_schema:
- #
- # - :gitlab_ci
- # - :gitlab_main
- #
- # The value can be determined by calling `Model.gitlab_schema` where the Model represents
- # the model for the child table.
- #
# How it works:
#
# When adding loose foreign key support to the table, a DELETE trigger is installed
@@ -69,23 +59,17 @@ module LooseForeignKey
end
on_delete_options = %i[async_delete async_nullify]
- gitlab_schema_options = [ApplicationRecord.gitlab_schema, Ci::ApplicationRecord.gitlab_schema]
unless on_delete_options.include?(symbolized_options[:on_delete]&.to_sym)
raise "Invalid on_delete option given: #{symbolized_options[:on_delete]}. Valid options: #{on_delete_options.join(', ')}"
end
- unless gitlab_schema_options.include?(symbolized_options[:gitlab_schema]&.to_sym)
- raise "Invalid gitlab_schema option given: #{symbolized_options[:gitlab_schema]}. Valid options: #{gitlab_schema_options.join(', ')}"
- end
-
definition = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
table_name.to_s,
to_table.to_s,
{
column: column.to_s,
- on_delete: symbolized_options[:on_delete].to_sym,
- gitlab_schema: symbolized_options[:gitlab_schema].to_sym
+ on_delete: symbolized_options[:on_delete].to_sym
}
)
diff --git a/app/models/concerns/merge_request_reviewer_state.rb b/app/models/concerns/merge_request_reviewer_state.rb
new file mode 100644
index 00000000000..216a3a0bd64
--- /dev/null
+++ b/app/models/concerns/merge_request_reviewer_state.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module MergeRequestReviewerState
+ extend ActiveSupport::Concern
+
+ included do
+ enum state: {
+ unreviewed: 0,
+ reviewed: 1,
+ attention_requested: 2
+ }
+
+ validates :state,
+ presence: true,
+ inclusion: { in: self.states.keys }
+
+ after_initialize :set_state, unless: :persisted?
+
+ def set_state
+ if Feature.enabled?(:mr_attention_requests, self.merge_request&.project, default_enabled: :yaml)
+ self.state = :attention_requested
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/milestoneable.rb b/app/models/concerns/milestoneable.rb
index c4f810ab9b1..12041b103f6 100644
--- a/app/models/concerns/milestoneable.rb
+++ b/app/models/concerns/milestoneable.rb
@@ -14,13 +14,12 @@ module Milestoneable
validate :milestone_is_valid
- scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :any_milestone, -> { where.not(milestone_id: nil) }
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
scope :without_particular_milestone, ->(title) { left_outer_joins(:milestone).where("milestones.title != ? OR milestone_id IS NULL", title) }
scope :any_release, -> { joins_milestone_releases }
scope :with_release, -> (tag, project_id) { joins_milestone_releases.where( milestones: { releases: { tag: tag, project_id: project_id } } ) }
- scope :without_particular_release, -> (tag, project_id) { joins_milestone_releases.where.not( milestones: { releases: { tag: tag, project_id: project_id } } ) }
+ scope :without_particular_release, -> (tag, project_id) { joins_milestone_releases.where.not(milestones: { releases: { tag: tag, project_id: project_id } }) }
scope :left_joins_milestones, -> { joins("LEFT OUTER JOIN milestones ON #{table_name}.milestone_id = milestones.id") }
scope :order_milestone_due_desc, -> { left_joins_milestones.reorder(Arel.sql('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date DESC')) }
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index f6d4e5bd27b..ea4fe5b27dc 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -98,6 +98,27 @@ module Noteable
.order('MIN(created_at), MIN(id)')
end
+ # This does not consider OutOfContextDiscussions in MRs
+ # where notes from commits are overriden so that they have
+ # the same discussion_id
+ def discussion_root_note_ids(notes_filter:)
+ relations = []
+
+ relations << discussion_notes.select(
+ "'notes' AS table_name",
+ 'discussion_id',
+ 'MIN(id) AS id',
+ 'MIN(created_at) AS created_at'
+ ).with_notes_filter(notes_filter)
+ .group(:discussion_id)
+
+ if notes_filter != UserPreference::NOTES_FILTERS[:only_comments]
+ relations += synthetic_note_ids_relations
+ end
+
+ Note.from_union(relations, remove_duplicates: false).fresh
+ end
+
def capped_notes_count(max)
notes.limit(max).count
end
@@ -179,6 +200,18 @@ module Noteable
project_email.sub('@', "-#{iid}@")
end
+
+ private
+
+ # Synthetic system notes don't have discussion IDs because these are generated dynamically
+ # in Ruby. These are always root notes anyway so we don't need to group by discussion ID.
+ def synthetic_note_ids_relations
+ [
+ resource_label_events.select("'resource_label_events'", "'NULL'", :id, :created_at),
+ resource_milestone_events.select("'resource_milestone_events'", "'NULL'", :id, :created_at),
+ resource_state_events.select("'resource_state_events'", "'NULL'", :id, :created_at)
+ ]
+ end
end
Noteable.extend(Noteable::ClassMethods)
diff --git a/app/models/concerns/reactive_service.rb b/app/models/concerns/reactive_service.rb
deleted file mode 100644
index c444f238944..00000000000
--- a/app/models/concerns/reactive_service.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module ReactiveService
- extend ActiveSupport::Concern
-
- included do
- include ReactiveCaching
-
- # Default cache key: class name + project_id
- self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
- self.reactive_cache_work_type = :external_dependency
- end
-end
diff --git a/app/models/concerns/security/latest_pipeline_information.rb b/app/models/concerns/security/latest_pipeline_information.rb
new file mode 100644
index 00000000000..87eae3cac68
--- /dev/null
+++ b/app/models/concerns/security/latest_pipeline_information.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Security
+ module LatestPipelineInformation
+ private
+
+ def scanner_enabled?(scan_type)
+ latest_builds_reports.include?(scan_type)
+ end
+
+ def latest_builds_reports(only_successful_builds: false)
+ strong_memoize("latest_builds_reports_#{only_successful_builds}") do
+ builds = latest_security_builds
+ builds = builds.select { |build| build.status == 'success' } if only_successful_builds
+ builds.flat_map do |build|
+ build.options[:artifacts][:reports].keys
+ end
+ end
+ end
+
+ def latest_security_builds
+ return [] unless latest_default_branch_pipeline
+
+ ::Security::SecurityJobsFinder.new(pipeline: latest_default_branch_pipeline).execute +
+ ::Security::LicenseComplianceJobsFinder.new(pipeline: latest_default_branch_pipeline).execute
+ end
+
+ def latest_default_branch_pipeline
+ strong_memoize(:pipeline) { latest_pipeline }
+ end
+
+ def auto_devops_source?
+ latest_default_branch_pipeline&.auto_devops_source?
+ end
+ end
+end
diff --git a/app/models/concerns/service_push_data_validations.rb b/app/models/concerns/service_push_data_validations.rb
deleted file mode 100644
index 451804a2c56..00000000000
--- a/app/models/concerns/service_push_data_validations.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-# This concern is used by registered integrations such as Integrations::TeamCity and
-# Integrations::DroneCi and adds methods to perform validations on the received
-# data.
-
-module ServicePushDataValidations
- extend ActiveSupport::Concern
-
- def merge_request_valid?(data)
- data.dig(:object_attributes, :state) == 'opened' && merge_request_unchecked?(data)
- end
-
- def push_valid?(data)
- data[:total_commits_count] > 0 &&
- !branch_removed?(data) &&
- # prefer merge request trigger over push to avoid double builds
- !opened_merge_requests?(data)
- end
-
- def tag_push_valid?(data)
- data[:total_commits_count] > 0 && !branch_removed?(data)
- end
-
- private
-
- def branch_removed?(data)
- Gitlab::Git.blank_ref?(data[:after])
- end
-
- def opened_merge_requests?(data)
- project.merge_requests
- .opened
- .from_project(project)
- .from_source_branches(Gitlab::Git.ref_name(data[:ref]))
- .exists?
- end
-
- def merge_request_unchecked?(data)
- MergeRequest.state_machines[:merge_status]
- .check_state?(data.dig(:object_attributes, :merge_status))
- end
-end
diff --git a/app/models/concerns/sha256_attribute.rb b/app/models/concerns/sha256_attribute.rb
index 17fda6c806c..3c906642b1a 100644
--- a/app/models/concerns/sha256_attribute.rb
+++ b/app/models/concerns/sha256_attribute.rb
@@ -39,7 +39,7 @@ module Sha256Attribute
end
def database_exists?
- Gitlab::Database.main.exists?
+ database.exists?
end
end
end
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index 27277bc5296..ba7c6c0cd8b 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -32,7 +32,7 @@ module ShaAttribute
end
def database_exists?
- Gitlab::Database.main.exists?
+ database.exists?
end
end
end
diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb
index 1c433a3275e..817a4465f91 100644
--- a/app/models/concerns/strip_attribute.rb
+++ b/app/models/concerns/strip_attribute.rb
@@ -2,7 +2,8 @@
# == Strip Attribute module
#
-# Contains functionality to clean attributes before validation
+# Contains functionality to remove leading and trailing
+# whitespace from the attribute before validation
#
# Usage:
#
diff --git a/app/models/concerns/timebox.rb b/app/models/concerns/timebox.rb
index 79cbe225e5a..3fe9d7f4d71 100644
--- a/app/models/concerns/timebox.rb
+++ b/app/models/concerns/timebox.rb
@@ -11,9 +11,7 @@ module Timebox
include StripAttribute
include FromUnion
- TimeboxStruct = Struct.new(:title, :name, :id) do
- include GlobalID::Identification
-
+ TimeboxStruct = Struct.new(:title, :name, :id, :class_name) do
# Ensure these models match the interface required for exporting
def serializable_hash(_opts = {})
{ title: title, name: name, id: id }
@@ -22,6 +20,10 @@ module Timebox
def self.declarative_policy_class
"TimeboxPolicy"
end
+
+ def to_global_id
+ ::Gitlab::GlobalId.build(self, model_name: class_name, id: id)
+ end
end
# Represents a "No Timebox" state used for filtering Issues and Merge
@@ -33,10 +35,10 @@ module Timebox
included do
# Defines the same constants above, but inside the including class.
- const_set :None, TimeboxStruct.new("No #{self.name}", "No #{self.name}", 0)
- const_set :Any, TimeboxStruct.new("Any #{self.name}", '', -1)
- const_set :Upcoming, TimeboxStruct.new('Upcoming', '#upcoming', -2)
- const_set :Started, TimeboxStruct.new('Started', '#started', -3)
+ const_set :None, TimeboxStruct.new("No #{self.name}", "No #{self.name}", 0, self.name)
+ const_set :Any, TimeboxStruct.new("Any #{self.name}", '', -1, self.name)
+ const_set :Upcoming, TimeboxStruct.new('Upcoming', '#upcoming', -2, self.name)
+ const_set :Started, TimeboxStruct.new('Started', '#started', -3, self.name)
alias_method :timebox_id, :id
diff --git a/app/models/concerns/transactions.rb b/app/models/concerns/transactions.rb
new file mode 100644
index 00000000000..a186ebc8475
--- /dev/null
+++ b/app/models/concerns/transactions.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Transactions
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # inside_transaction? will return true if the caller is running within a
+ # transaction. Handles special cases when running inside a test environment,
+ # where tests may be wrapped in transactions
+ def inside_transaction?
+ base = Rails.env.test? ? @open_transactions_baseline.to_i : 0
+
+ connection.open_transactions > base
+ end
+
+ # These methods that access @open_transactions_baseline are not thread-safe.
+ # These are fine though because we only call these in RSpec's main thread.
+ # If we decide to run specs multi-threaded, we would need to use something
+ # like ThreadGroup to keep track of this value
+ def set_open_transactions_baseline
+ @open_transactions_baseline = connection.open_transactions
+ end
+
+ def reset_open_transactions_baseline
+ @open_transactions_baseline = 0
+ end
+ end
+end
diff --git a/app/models/concerns/ttl_expirable.rb b/app/models/concerns/ttl_expirable.rb
index 00abe0a06e6..6d89521255c 100644
--- a/app/models/concerns/ttl_expirable.rb
+++ b/app/models/concerns/ttl_expirable.rb
@@ -5,10 +5,11 @@ module TtlExpirable
included do
validates :status, presence: true
+ default_value_for :read_at, Time.zone.now
enum status: { default: 0, expired: 1, processing: 2, error: 3 }
- scope :updated_before, ->(number_of_days) { where("updated_at <= ?", Time.zone.now - number_of_days.days) }
+ scope :read_before, ->(number_of_days) { where("read_at <= ?", Time.zone.now - number_of_days.days) }
scope :active, -> { where(status: :default) }
scope :lock_next_by, ->(sort) do
@@ -17,4 +18,8 @@ module TtlExpirable
.lock('FOR UPDATE SKIP LOCKED')
end
end
+
+ def read!
+ self.update(read_at: Time.zone.now)
+ end
end
diff --git a/app/models/concerns/update_highest_role.rb b/app/models/concerns/update_highest_role.rb
index 6432cc794a5..2b0ec5c7e21 100644
--- a/app/models/concerns/update_highest_role.rb
+++ b/app/models/concerns/update_highest_role.rb
@@ -15,7 +15,7 @@ module UpdateHighestRole
# Schedule a Sidekiq job to update the highest role for a User
#
# The job will be called outside of a transaction in order to ensure the changes
- # to be commited before attempting to update the highest role.
+ # to be committed before attempting to update the highest role.
# The exlusive lease will not be released after completion to prevent multiple jobs
# being executed during the defined timeout.
def update_highest_role
diff --git a/app/models/concerns/x509_serial_number_attribute.rb b/app/models/concerns/x509_serial_number_attribute.rb
index dfb1e151b41..e51ed95bf70 100644
--- a/app/models/concerns/x509_serial_number_attribute.rb
+++ b/app/models/concerns/x509_serial_number_attribute.rb
@@ -39,7 +39,7 @@ module X509SerialNumberAttribute
end
def database_exists?
- Gitlab::Database.main.exists?
+ database.exists?
end
end
end