diff options
Diffstat (limited to 'app/models/concerns')
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 |