summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/ci/bridge.rb1
-rw-r--r--app/models/ci/build.rb19
-rw-r--r--app/models/ci/job_artifact.rb15
-rw-r--r--app/models/ci/processable.rb6
-rw-r--r--app/models/ci/runner.rb6
-rw-r--r--app/models/clusters/applications/fluentd.rb101
-rw-r--r--app/models/clusters/applications/ingress.rb11
-rw-r--r--app/models/clusters/cluster.rb4
-rw-r--r--app/models/concerns/ci/has_ref.rb2
-rw-r--r--app/models/concerns/ci/pipeline_delegator.rb20
-rw-r--r--app/models/concerns/issuable.rb21
-rw-r--r--app/models/concerns/notification_branch_selection.rb14
-rw-r--r--app/models/concerns/project_features_compatibility.rb4
-rw-r--r--app/models/diff_discussion.rb1
-rw-r--r--app/models/diff_note_position.rb2
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/import_failure.rb7
-rw-r--r--app/models/jira_import_state.rb17
-rw-r--r--app/models/lfs_object.rb13
-rw-r--r--app/models/merge_request.rb22
-rw-r--r--app/models/merge_request_diff.rb19
-rw-r--r--app/models/metrics/dashboard/annotation.rb5
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/project.rb19
-rw-r--r--app/models/project_feature.rb17
-rw-r--r--app/models/project_import_state.rb4
-rw-r--r--app/models/project_services/chat_notification_service.rb10
-rw-r--r--app/models/project_services/discord_service.rb2
-rw-r--r--app/models/project_services/emails_on_push_service.rb2
-rw-r--r--app/models/project_services/hangouts_chat_service.rb2
-rw-r--r--app/models/project_services/issue_tracker_service.rb2
-rw-r--r--app/models/project_services/microsoft_teams_service.rb2
-rw-r--r--app/models/project_services/pipelines_email_service.rb2
-rw-r--r--app/models/project_services/prometheus_service.rb6
-rw-r--r--app/models/project_services/unify_circuit_service.rb2
-rw-r--r--app/models/resource_label_event.rb2
-rw-r--r--app/models/resource_milestone_event.rb6
-rw-r--r--app/models/route.rb4
-rw-r--r--app/models/terraform/state.rb15
-rw-r--r--app/models/user.rb17
-rw-r--r--app/models/user_type_enums.rb2
41 files changed, 302 insertions, 128 deletions
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
index fa0619f35b0..76882dfcb0d 100644
--- a/app/models/ci/bridge.rb
+++ b/app/models/ci/bridge.rb
@@ -3,7 +3,6 @@
module Ci
class Bridge < Ci::Processable
include Ci::Contextable
- include Ci::PipelineDelegator
include Ci::Metadatable
include Importable
include AfterCommitQueue
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 74a329dccf4..8bc75b6c164 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -4,7 +4,6 @@ module Ci
class Build < Ci::Processable
include Ci::Metadatable
include Ci::Contextable
- include Ci::PipelineDelegator
include TokenAuthenticatable
include AfterCommitQueue
include ObjectStorage::BackgroundMove
@@ -526,6 +525,7 @@ module Ci
strong_memoize(:variables) do
Gitlab::Ci::Variables::Collection.new
.concat(persisted_variables)
+ .concat(job_jwt_variables)
.concat(scoped_variables)
.concat(job_variables)
.concat(environment_changed_page_variables)
@@ -591,13 +591,7 @@ module Ci
def merge_request
strong_memoize(:merge_request) do
- merge_requests = MergeRequest.includes(:latest_merge_request_diff)
- .where(source_branch: ref, source_project: pipeline.project)
- .reorder(iid: :desc)
-
- merge_requests.find do |merge_request|
- merge_request.commit_shas.include?(pipeline.sha)
- end
+ pipeline.all_merge_requests.order(iid: :asc).first
end
end
@@ -981,6 +975,15 @@ module Ci
def has_expiring_artifacts?
artifacts_expire_at.present? && artifacts_expire_at > Time.now
end
+
+ def job_jwt_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ break variables unless Feature.enabled?(:ci_job_jwt, project, default_enabled: true)
+
+ jwt = Gitlab::Ci::Jwt.for_build(self)
+ variables.append(key: 'CI_JOB_JWT', value: jwt, public: false, masked: true)
+ end
+ end
end
end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index c4ac10814a9..ef0701b3874 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -73,14 +73,12 @@ module Ci
validates :file_format, presence: true, unless: :trace?, on: :create
validate :valid_file_format?, unless: :trace?, on: :create
-
before_save :set_size, if: :file_changed?
- before_save :set_file_store, if: ->(job_artifact) { job_artifact.file_store.nil? }
-
- after_save :update_file_store, if: :saved_change_to_file?
update_project_statistics project_statistics_name: :build_artifacts_size
+ after_save :update_file_store, if: :saved_change_to_file?
+
scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
scope :with_files_stored_remotely, -> { where(file_store: ::JobArtifactUploader::Store::REMOTE) }
scope :for_sha, ->(sha, project_id) { joins(job: :pipeline).where(ci_pipelines: { sha: sha, project_id: project_id }) }
@@ -228,15 +226,6 @@ module Ci
self.size = file.size
end
- def set_file_store
- self.file_store =
- if JobArtifactUploader.object_store_enabled? && JobArtifactUploader.direct_upload_enabled?
- JobArtifactUploader::Store::REMOTE
- else
- file.object_store
- end
- end
-
def project_destroyed?
# Use job.project to avoid extra DB query for project
job.project.pending_delete?
diff --git a/app/models/ci/processable.rb b/app/models/ci/processable.rb
index 4bc8f26ec92..c123bd7c33b 100644
--- a/app/models/ci/processable.rb
+++ b/app/models/ci/processable.rb
@@ -51,6 +51,12 @@ module Ci
validates :type, presence: true
validates :scheduling_type, presence: true, on: :create, if: :validate_scheduling_type?
+ delegate :merge_request?,
+ :merge_request_ref?,
+ :legacy_detached_merge_request_pipeline?,
+ :merge_train_pipeline?,
+ to: :pipeline
+
def aggregated_needs_names
read_attribute(:aggregated_needs_names)
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 690aa978716..d4e9217ff9f 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -35,6 +35,7 @@ module Ci
AVAILABLE_SCOPES = (AVAILABLE_TYPES_LEGACY + AVAILABLE_TYPES + AVAILABLE_STATUSES).freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
+ MINUTES_COST_FACTOR_FIELDS = %i[public_projects_minutes_cost_factor private_projects_minutes_cost_factor].freeze
ignore_column :is_shared, remove_after: '2019-12-15', remove_with: '12.6'
@@ -137,6 +138,11 @@ module Ci
numericality: { greater_than_or_equal_to: 600,
message: 'needs to be at least 10 minutes' }
+ validates :public_projects_minutes_cost_factor, :private_projects_minutes_cost_factor,
+ allow_nil: false,
+ numericality: { greater_than_or_equal_to: 0.0,
+ message: 'needs to be non-negative' }
+
# Searches for runners matching the given query.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
diff --git a/app/models/clusters/applications/fluentd.rb b/app/models/clusters/applications/fluentd.rb
new file mode 100644
index 00000000000..a33b1e39ace
--- /dev/null
+++ b/app/models/clusters/applications/fluentd.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class Fluentd < ApplicationRecord
+ VERSION = '2.4.0'
+
+ self.table_name = 'clusters_applications_fluentd'
+
+ include ::Clusters::Concerns::ApplicationCore
+ include ::Clusters::Concerns::ApplicationStatus
+ include ::Clusters::Concerns::ApplicationVersion
+ include ::Clusters::Concerns::ApplicationData
+
+ default_value_for :version, VERSION
+ default_value_for :port, 514
+ default_value_for :protocol, :tcp
+
+ enum protocol: { tcp: 0, udp: 1 }
+
+ def chart
+ 'stable/fluentd'
+ end
+
+ def install_command
+ Gitlab::Kubernetes::Helm::InstallCommand.new(
+ name: 'fluentd',
+ repository: repository,
+ version: VERSION,
+ rbac: cluster.platform_kubernetes_rbac?,
+ chart: chart,
+ files: files
+ )
+ end
+
+ def values
+ content_values.to_yaml
+ end
+
+ private
+
+ def content_values
+ YAML.load_file(chart_values_file).deep_merge!(specification)
+ end
+
+ def specification
+ {
+ "configMaps" => {
+ "output.conf" => output_configuration_content,
+ "general.conf" => general_configuration_content
+ }
+ }
+ end
+
+ def output_configuration_content
+ <<~EOF
+ <match kubernetes.**>
+ @type remote_syslog
+ @id out_kube_remote_syslog
+ host #{host}
+ port #{port}
+ program fluentd
+ hostname ${kubernetes_host}
+ protocol #{protocol}
+ packet_size 65535
+ <buffer kubernetes_host>
+ </buffer>
+ <format>
+ @type ltsv
+ </format>
+ </match>
+ EOF
+ end
+
+ def general_configuration_content
+ <<~EOF
+ <match fluent.**>
+ @type null
+ </match>
+ <source>
+ @type http
+ port 9880
+ bind 0.0.0.0
+ </source>
+ <source>
+ @type tail
+ @id in_tail_container_logs
+ path /var/log/containers/*#{Ingress::MODSECURITY_LOG_CONTAINER_NAME}*.log
+ pos_file /var/log/fluentd-containers.log.pos
+ tag kubernetes.*
+ read_from_head true
+ <parse>
+ @type json
+ time_format %Y-%m-%dT%H:%M:%S.%NZ
+ </parse>
+ </source>
+ EOF
+ end
+ end
+ end
+end
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index baf34e916f8..5985e08d73e 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -30,7 +30,6 @@ module Clusters
enum modsecurity_mode: { logging: 0, blocking: 1 }
FETCH_IP_ADDRESS_DELAY = 30.seconds
- MODSEC_SIDECAR_INITIAL_DELAY_SECONDS = 10
state_machine :status do
after_transition any => [:installed] do |application|
@@ -108,11 +107,13 @@ module Clusters
"readOnly" => true
}
],
- "startupProbe" => {
+ "livenessProbe" => {
"exec" => {
- "command" => ["ls", "/var/log/modsec"]
- },
- "initialDelaySeconds" => MODSEC_SIDECAR_INITIAL_DELAY_SECONDS
+ "command" => [
+ "ls",
+ "/var/log/modsec/audit.log"
+ ]
+ }
}
}
],
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 9ef3d64f21a..430a9b3c43e 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -19,7 +19,8 @@ module Clusters
Clusters::Applications::Runner.application_name => Clusters::Applications::Runner,
Clusters::Applications::Jupyter.application_name => Clusters::Applications::Jupyter,
Clusters::Applications::Knative.application_name => Clusters::Applications::Knative,
- Clusters::Applications::ElasticStack.application_name => Clusters::Applications::ElasticStack
+ Clusters::Applications::ElasticStack.application_name => Clusters::Applications::ElasticStack,
+ Clusters::Applications::Fluentd.application_name => Clusters::Applications::Fluentd
}.freeze
DEFAULT_ENVIRONMENT = '*'
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'
@@ -57,6 +58,7 @@ module Clusters
has_one_cluster_application :jupyter
has_one_cluster_application :knative
has_one_cluster_application :elastic_stack
+ has_one_cluster_application :fluentd
has_many :kubernetes_namespaces
has_many :metrics_dashboard_annotations, class_name: 'Metrics::Dashboard::Annotation', inverse_of: :cluster
diff --git a/app/models/concerns/ci/has_ref.rb b/app/models/concerns/ci/has_ref.rb
index cf57ff47743..e2d459ea70e 100644
--- a/app/models/concerns/ci/has_ref.rb
+++ b/app/models/concerns/ci/has_ref.rb
@@ -2,7 +2,7 @@
##
# We will disable `ref` and `sha` attributes in `Ci::Build` in the future
-# and remove this module in favor of Ci::PipelineDelegator.
+# and remove this module in favor of Ci::Processable.
module Ci
module HasRef
extend ActiveSupport::Concern
diff --git a/app/models/concerns/ci/pipeline_delegator.rb b/app/models/concerns/ci/pipeline_delegator.rb
deleted file mode 100644
index 68ad0fcee31..00000000000
--- a/app/models/concerns/ci/pipeline_delegator.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-##
-# This module is mainly used by child associations of `Ci::Pipeline` that needs to look up
-# single source of truth. For example, `Ci::Build` has `git_ref` method, which behaves
-# slightly different from `Ci::Pipeline`'s `git_ref`. This is very confusing as
-# the system could behave differently time to time.
-# We should have a single interface in `Ci::Pipeline` and access the method always.
-module Ci
- module PipelineDelegator
- extend ActiveSupport::Concern
-
- included do
- delegate :merge_request?,
- :merge_request_ref?,
- :legacy_detached_merge_request_pipeline?,
- :merge_train_pipeline?, to: :pipeline
- end
- end
-end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 7300283f086..37f2209b9d2 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -116,6 +116,7 @@ module Issuable
# rubocop:enable GitlabSecurity/SqlInjection
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
+ scope :with_label_ids, ->(label_ids) { joins(:label_links).where(label_links: { label_id: label_ids }) }
scope :any_label, -> { joins(:label_links).group(:id) }
scope :join_project, -> { joins(:project) }
scope :inc_notes_with_associations, -> { includes(notes: [:project, :author, :award_emoji]) }
@@ -131,8 +132,21 @@ module Issuable
strip_attributes :title
- def self.locking_enabled?
- false
+ class << self
+ def labels_hash
+ issue_labels = Hash.new { |h, k| h[k] = [] }
+
+ relation = unscoped.where(id: self.select(:id)).eager_load(:labels)
+ relation.pluck(:id, 'labels.title').each do |issue_id, label|
+ issue_labels[issue_id] << label if label.present?
+ end
+
+ issue_labels
+ end
+
+ def locking_enabled?
+ false
+ end
end
# We want to use optimistic lock for cases when only title or description are involved
@@ -478,5 +492,4 @@ module Issuable
end
end
-Issuable.prepend_if_ee('EE::Issuable') # rubocop: disable Cop/InjectEnterpriseEditionModule
-Issuable::ClassMethods.prepend_if_ee('EE::Issuable::ClassMethods')
+Issuable.prepend_if_ee('EE::Issuable')
diff --git a/app/models/concerns/notification_branch_selection.rb b/app/models/concerns/notification_branch_selection.rb
index 7f00b652530..2354335469a 100644
--- a/app/models/concerns/notification_branch_selection.rb
+++ b/app/models/concerns/notification_branch_selection.rb
@@ -6,12 +6,14 @@
module NotificationBranchSelection
extend ActiveSupport::Concern
- BRANCH_CHOICES = [
- [_('All branches'), 'all'],
- [_('Default branch'), 'default'],
- [_('Protected branches'), 'protected'],
- [_('Default branch and protected branches'), 'default_and_protected']
- ].freeze
+ def branch_choices
+ [
+ [_('All branches'), 'all'].freeze,
+ [_('Default branch'), 'default'].freeze,
+ [_('Protected branches'), 'protected'].freeze,
+ [_('Default branch and protected branches'), 'default_and_protected'].freeze
+ ].freeze
+ end
def notify_for_branch?(data)
ref = if data[:ref]
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index 76d26500267..cedcf164a49 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(:pages_access_level, value)
end
+ def metrics_dashboard_access_level=(value)
+ write_feature_attribute_string(:metrics_dashboard_access_level, value)
+ end
+
private
def write_feature_attribute_boolean(field, value)
diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb
index 93e3ebf7896..f9e2f00b9f3 100644
--- a/app/models/diff_discussion.rb
+++ b/app/models/diff_discussion.rb
@@ -13,6 +13,7 @@ class DiffDiscussion < Discussion
delegate :position,
:original_position,
:change_position,
+ :diff_note_positions,
:on_text?,
:on_image?,
diff --git a/app/models/diff_note_position.rb b/app/models/diff_note_position.rb
index 716a56c6430..a25b0def643 100644
--- a/app/models/diff_note_position.rb
+++ b/app/models/diff_note_position.rb
@@ -2,6 +2,7 @@
class DiffNotePosition < ApplicationRecord
belongs_to :note
+ attr_accessor :line_range
enum diff_content_type: {
text: 0,
@@ -42,6 +43,7 @@ class DiffNotePosition < ApplicationRecord
def self.position_to_attrs(position)
position_attrs = position.to_h
position_attrs[:diff_content_type] = position_attrs.delete(:position_type)
+ position_attrs.delete(:line_range)
position_attrs
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index f4eaa581d54..55a2c4ba9a9 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -72,7 +72,7 @@ class Group < Namespace
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :name,
format: { with: Gitlab::Regex.group_name_regex,
- message: Gitlab::Regex.group_name_regex_message }
+ message: Gitlab::Regex.group_name_regex_message }, if: :name_changed?
add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
diff --git a/app/models/import_failure.rb b/app/models/import_failure.rb
index a1e03218640..109c0c82487 100644
--- a/app/models/import_failure.rb
+++ b/app/models/import_failure.rb
@@ -6,4 +6,11 @@ class ImportFailure < ApplicationRecord
validates :project, presence: true, unless: :group
validates :group, presence: true, unless: :project
+
+ # Returns any `import_failures` for relations that were unrecoverable errors or failed after
+ # several retries. An import can be successful even if some relations failed to import correctly.
+ # A retry_count of 0 indicates that either no retries were attempted, or they were exceeded.
+ scope :hard_failures_by_correlation_id, ->(correlation_id) {
+ where(correlation_id_value: correlation_id, retry_count: 0).order(created_at: :desc)
+ }
end
diff --git a/app/models/jira_import_state.rb b/app/models/jira_import_state.rb
index 543ee77917c..bde2795e7b8 100644
--- a/app/models/jira_import_state.rb
+++ b/app/models/jira_import_state.rb
@@ -53,6 +53,7 @@ class JiraImportState < ApplicationRecord
before_transition any => :finished do |state, _|
InternalId.flush_records!(project: state.project)
state.project.update_project_counter_caches
+ state.store_issue_counts
end
after_transition any => :finished do |state, _|
@@ -80,4 +81,20 @@ class JiraImportState < ApplicationRecord
def non_initial?
!initial?
end
+
+ def store_issue_counts
+ import_label_id = Gitlab::JiraImport.get_import_label_id(project.id)
+
+ failed_to_import_count = Gitlab::JiraImport.issue_failures(project.id)
+ successfully_imported_count = project.issues.with_label_ids(import_label_id).count
+ total_issue_count = successfully_imported_count + failed_to_import_count
+
+ update(
+ {
+ failed_to_import_count: failed_to_import_count,
+ imported_issues_count: successfully_imported_count,
+ total_issue_count: total_issue_count
+ }
+ )
+ end
end
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index c5233deaa96..6a86aebae39 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -17,8 +17,6 @@ class LfsObject < ApplicationRecord
mount_uploader :file, LfsObjectUploader
- before_save :set_file_store, if: ->(lfs_object) { lfs_object.file_store.nil? }
-
after_save :update_file_store, if: :saved_change_to_file?
def self.not_linked_to_project(project)
@@ -57,17 +55,6 @@ class LfsObject < ApplicationRecord
def self.calculate_oid(path)
self.hexdigest(path)
end
-
- private
-
- def set_file_store
- self.file_store =
- if LfsObjectUploader.object_store_enabled? && LfsObjectUploader.direct_upload_enabled?
- LfsObjectUploader::Store::REMOTE
- else
- file.object_store
- end
- end
end
LfsObject.prepend_if_ee('EE::LfsObject')
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index b9acb539404..9939167e74f 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -167,20 +167,22 @@ class MergeRequest < ApplicationRecord
end
event :mark_as_checking do
- transition [:unchecked, :cannot_be_merged_recheck] => :checking
+ transition unchecked: :checking
+ transition cannot_be_merged_recheck: :cannot_be_merged_rechecking
end
event :mark_as_mergeable do
- transition [:unchecked, :cannot_be_merged_recheck, :checking] => :can_be_merged
+ transition [:unchecked, :cannot_be_merged_recheck, :checking, :cannot_be_merged_rechecking] => :can_be_merged
end
event :mark_as_unmergeable do
- transition [:unchecked, :cannot_be_merged_recheck, :checking] => :cannot_be_merged
+ transition [:unchecked, :cannot_be_merged_recheck, :checking, :cannot_be_merged_rechecking] => :cannot_be_merged
end
state :unchecked
state :cannot_be_merged_recheck
state :checking
+ state :cannot_be_merged_rechecking
state :can_be_merged
state :cannot_be_merged
@@ -189,7 +191,7 @@ class MergeRequest < ApplicationRecord
end
# rubocop: disable CodeReuse/ServiceClass
- after_transition unchecked: :cannot_be_merged do |merge_request, transition|
+ after_transition [:unchecked, :checking] => :cannot_be_merged do |merge_request, transition|
if merge_request.notify_conflict?
NotificationService.new.merge_request_unmergeable(merge_request)
TodoService.new.merge_request_became_unmergeable(merge_request)
@@ -202,6 +204,12 @@ class MergeRequest < ApplicationRecord
end
end
+ # Returns current merge_status except it returns `cannot_be_merged_rechecking` as `checking`
+ # to avoid exposing unnecessary internal state
+ def public_merge_status
+ cannot_be_merged_rechecking? ? 'checking' : merge_status
+ end
+
validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
validates :source_branch, presence: true
validates :target_project, presence: true
@@ -569,13 +577,13 @@ class MergeRequest < ApplicationRecord
merge_request_diff&.real_size || diff_stats&.real_size || diffs.real_size
end
- def modified_paths(past_merge_request_diff: nil)
+ def modified_paths(past_merge_request_diff: nil, fallback_on_overflow: false)
if past_merge_request_diff
- past_merge_request_diff.modified_paths
+ past_merge_request_diff.modified_paths(fallback_on_overflow: fallback_on_overflow)
elsif compare
diff_stats&.paths || compare.modified_paths
else
- merge_request_diff.modified_paths
+ merge_request_diff.modified_paths(fallback_on_overflow: fallback_on_overflow)
end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 9136c6cc5d4..7b15d21c095 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -366,9 +366,22 @@ class MergeRequestDiff < ApplicationRecord
end
# rubocop: enable CodeReuse/ServiceClass
- def modified_paths
- strong_memoize(:modified_paths) do
- merge_request_diff_files.pluck(:new_path, :old_path).flatten.uniq
+ def modified_paths(fallback_on_overflow: false)
+ if fallback_on_overflow && overflow?
+ # This is an extremely slow means to find the modified paths for a given
+ # MergeRequestDiff. This should be avoided, except where the limit of
+ # 1_000 (as of %12.10) entries returned by the default behavior is an
+ # issue.
+ strong_memoize(:overflowed_modified_paths) do
+ project.repository.diff_stats(
+ base_commit_sha,
+ head_commit_sha
+ ).paths
+ end
+ else
+ strong_memoize(:modified_paths) do
+ merge_request_diff_files.pluck(:new_path, :old_path).flatten.uniq
+ end
end
end
diff --git a/app/models/metrics/dashboard/annotation.rb b/app/models/metrics/dashboard/annotation.rb
index 2f1b6527742..8166880f0c9 100644
--- a/app/models/metrics/dashboard/annotation.rb
+++ b/app/models/metrics/dashboard/annotation.rb
@@ -15,6 +15,11 @@ module Metrics
validate :single_ownership
validate :orphaned_annotation
+ scope :after, ->(after) { where('starting_at >= ?', after) }
+ scope :before, ->(before) { where('starting_at <= ?', before) }
+
+ scope :for_dashboard, ->(dashboard_path) { where(dashboard_path: dashboard_path) }
+
private
def single_ownership
diff --git a/app/models/note.rb b/app/models/note.rb
index e6ad7c2227f..a2a711c987f 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -125,7 +125,7 @@ class Note < ApplicationRecord
scope :inc_author, -> { includes(:author) }
scope :inc_relations_for_view, -> do
includes(:project, { author: :status }, :updated_by, :resolved_by, :award_emoji,
- { system_note_metadata: :description_version }, :note_diff_file, :suggestions)
+ { system_note_metadata: :description_version }, :note_diff_file, :diff_note_positions, :suggestions)
end
scope :with_notes_filter, -> (notes_filter) do
diff --git a/app/models/project.rb b/app/models/project.rb
index 443b44dd023..79785bfce85 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -340,7 +340,7 @@ class Project < ApplicationRecord
:pages_enabled?, :public_pages?, :private_pages?,
:merge_requests_access_level, :forking_access_level, :issues_access_level,
:wiki_access_level, :snippets_access_level, :builds_access_level,
- :repository_access_level, :pages_access_level,
+ :repository_access_level, :pages_access_level, :metrics_dashboard_access_level,
to: :project_feature, allow_nil: true
delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?,
prefix: :import, to: :import_state, allow_nil: true
@@ -415,7 +415,6 @@ class Project < ApplicationRecord
scope :sorted_by_activity, -> { reorder(Arel.sql("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC")) }
scope :sorted_by_stars_desc, -> { reorder(self.arel_table['star_count'].desc) }
scope :sorted_by_stars_asc, -> { reorder(self.arel_table['star_count'].asc) }
- scope :sorted_by_name_asc_limited, ->(limit) { reorder(name: :asc).limit(limit) }
# Sometimes queries (e.g. using CTEs) require explicit disambiguation with table name
scope :projects_order_id_desc, -> { reorder(self.arel_table['id'].desc) }
@@ -591,7 +590,7 @@ class Project < ApplicationRecord
#
# query - The search query as a String.
def search(query, include_namespace: false)
- if include_namespace && Feature.enabled?(:project_search_by_full_path, default_enabled: true)
+ if include_namespace
joins(:route).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name], :description])
else
fuzzy_search(query, [:path, :name, :description])
@@ -774,10 +773,6 @@ class Project < ApplicationRecord
{ scope: :project, status: auto_devops&.enabled || Feature.enabled?(:force_autodevops_on_by_default, self) }
end
- def daily_statistics_enabled?
- Feature.enabled?(:project_daily_statistics, self, default_enabled: true)
- end
-
def unlink_forks_upon_visibility_decrease_enabled?
Feature.enabled?(:unlink_fork_network_upon_visibility_decrease, self, default_enabled: true)
end
@@ -866,6 +861,16 @@ class Project < ApplicationRecord
latest_jira_import&.status || 'initial'
end
+ def validate_jira_import_settings!(user: nil)
+ raise Projects::ImportService::Error, _('Jira import feature is disabled.') unless jira_issues_import_feature_flag_enabled?
+ raise Projects::ImportService::Error, _('Jira integration not configured.') unless jira_service&.active?
+
+ return unless user
+
+ raise Projects::ImportService::Error, _('Cannot import because issues are not available in this project.') unless feature_available?(:issues, user)
+ raise Projects::ImportService::Error, _('You do not have permissions to run the import.') unless user.can?(:admin_project, self)
+ end
+
def human_import_status_name
import_state&.human_status_name || 'none'
end
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index a9753c3c53a..31a3fa12c00 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -22,7 +22,7 @@ class ProjectFeature < ApplicationRecord
ENABLED = 20
PUBLIC = 30
- FEATURES = %i(issues forking merge_requests wiki snippets builds repository pages).freeze
+ FEATURES = %i(issues forking merge_requests wiki snippets builds repository pages metrics_dashboard).freeze
PRIVATE_FEATURES_MIN_ACCESS_LEVEL = { merge_requests: Gitlab::Access::REPORTER }.freeze
PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT = { repository: Gitlab::Access::REPORTER }.freeze
STRING_OPTIONS = HashWithIndifferentAccess.new({
@@ -90,13 +90,14 @@ class ProjectFeature < ApplicationRecord
validate :repository_children_level
validate :allowed_access_levels
- default_value_for :builds_access_level, value: ENABLED, allows_nil: false
- default_value_for :issues_access_level, value: ENABLED, allows_nil: false
- default_value_for :forking_access_level, value: ENABLED, allows_nil: false
- default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false
- default_value_for :snippets_access_level, value: ENABLED, allows_nil: false
- default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
- default_value_for :repository_access_level, value: ENABLED, allows_nil: false
+ default_value_for :builds_access_level, value: ENABLED, allows_nil: false
+ default_value_for :issues_access_level, value: ENABLED, allows_nil: false
+ default_value_for :forking_access_level, value: ENABLED, allows_nil: false
+ default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false
+ default_value_for :snippets_access_level, value: ENABLED, allows_nil: false
+ default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
+ default_value_for :repository_access_level, value: ENABLED, allows_nil: false
+ default_value_for :metrics_dashboard_access_level, value: PRIVATE, allows_nil: false
default_value_for(:pages_access_level, allows_nil: false) do |feature|
if ::Gitlab::Pages.access_control_is_forced?
diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb
index f58b8dc624d..e434ea58729 100644
--- a/app/models/project_import_state.rb
+++ b/app/models/project_import_state.rb
@@ -72,6 +72,10 @@ class ProjectImportState < ApplicationRecord
end
end
+ def relation_hard_failures(limit:)
+ project.import_failures.hard_failures_by_correlation_id(correlation_id).limit(limit)
+ end
+
def mark_as_failed(error_message)
original_errors = errors.dup
sanitized_message = Gitlab::UrlSanitizer.sanitize(error_message)
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index 1ec983223f3..c9e97efb4ac 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -59,11 +59,11 @@ class ChatNotificationService < Service
def default_fields
[
- { type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}", required: true },
- { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
- { type: 'checkbox', name: 'notify_only_broken_pipelines' },
- { type: 'select', name: 'branches_to_be_notified', choices: BRANCH_CHOICES }
- ]
+ { type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}", required: true }.freeze,
+ { type: 'text', name: 'username', placeholder: 'e.g. GitLab' }.freeze,
+ { type: 'checkbox', name: 'notify_only_broken_pipelines' }.freeze,
+ { type: 'select', name: 'branches_to_be_notified', choices: branch_choices }.freeze
+ ].freeze
end
def execute(data)
diff --git a/app/models/project_services/discord_service.rb b/app/models/project_services/discord_service.rb
index 294b286f073..941b7f64263 100644
--- a/app/models/project_services/discord_service.rb
+++ b/app/models/project_services/discord_service.rb
@@ -44,7 +44,7 @@ class DiscordService < ChatNotificationService
[
{ type: "text", name: "webhook", placeholder: "e.g. https://discordapp.com/api/webhooks/…" },
{ type: "checkbox", name: "notify_only_broken_pipelines" },
- { type: 'select', name: 'branches_to_be_notified', choices: BRANCH_CHOICES }
+ { type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index dd2f1359e76..01d8647d439 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -66,7 +66,7 @@ class EmailsOnPushService < Service
help: s_("EmailsOnPushService|Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. %{domains}).") % { domains: domains } },
{ type: 'checkbox', name: 'disable_diffs', title: s_("EmailsOnPushService|Disable code diffs"),
help: s_("EmailsOnPushService|Don't include possibly sensitive code diffs in notification body.") },
- { type: 'select', name: 'branches_to_be_notified', choices: BRANCH_CHOICES },
+ { type: 'select', name: 'branches_to_be_notified', choices: branch_choices },
{ type: 'textarea', name: 'recipients', placeholder: s_('EmailsOnPushService|Emails separated by whitespace') }
]
end
diff --git a/app/models/project_services/hangouts_chat_service.rb b/app/models/project_services/hangouts_chat_service.rb
index d105bd012d6..299a306add7 100644
--- a/app/models/project_services/hangouts_chat_service.rb
+++ b/app/models/project_services/hangouts_chat_service.rb
@@ -44,7 +44,7 @@ class HangoutsChatService < ChatNotificationService
[
{ type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
- { type: 'select', name: 'branches_to_be_notified', choices: BRANCH_CHOICES }
+ { type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 3f7e8a720aa..f5d6ae10469 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -172,7 +172,7 @@ class IssueTrackerService < Service
end
def one_issue_tracker
- return if template?
+ return if template? || instance?
return if project.blank?
if project.services.external_issue_trackers.where.not(id: id).any?
diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb
index 111d010d672..e8e12a9a206 100644
--- a/app/models/project_services/microsoft_teams_service.rb
+++ b/app/models/project_services/microsoft_teams_service.rb
@@ -42,7 +42,7 @@ class MicrosoftTeamsService < ChatNotificationService
[
{ type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
- { type: 'select', name: 'branches_to_be_notified', choices: BRANCH_CHOICES }
+ { type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb
index b5e5afb6ea5..a58a264de5e 100644
--- a/app/models/project_services/pipelines_email_service.rb
+++ b/app/models/project_services/pipelines_email_service.rb
@@ -72,7 +72,7 @@ class PipelinesEmailService < Service
name: 'notify_only_broken_pipelines' },
{ type: 'select',
name: 'branches_to_be_notified',
- choices: BRANCH_CHOICES }
+ choices: branch_choices }
]
end
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index 1a85289a04f..4a28d1ff2b0 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -36,10 +36,6 @@ class PrometheusService < MonitoringService
false
end
- def editable?
- manual_configuration? || !prometheus_available?
- end
-
def title
'Prometheus'
end
@@ -53,8 +49,6 @@ class PrometheusService < MonitoringService
end
def fields
- return [] unless editable?
-
[
{
type: 'checkbox',
diff --git a/app/models/project_services/unify_circuit_service.rb b/app/models/project_services/unify_circuit_service.rb
index 06f2d10f83b..1e12179e62a 100644
--- a/app/models/project_services/unify_circuit_service.rb
+++ b/app/models/project_services/unify_circuit_service.rb
@@ -38,7 +38,7 @@ class UnifyCircuitService < ChatNotificationService
[
{ type: 'text', name: 'webhook', placeholder: "e.g. https://circuit.com/rest/v2/webhooks/incoming/…", required: true },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
- { type: 'select', name: 'branches_to_be_notified', choices: BRANCH_CHOICES }
+ { type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
end
diff --git a/app/models/resource_label_event.rb b/app/models/resource_label_event.rb
index 8e66310f0c5..cd47c154eef 100644
--- a/app/models/resource_label_event.rb
+++ b/app/models/resource_label_event.rb
@@ -56,7 +56,7 @@ class ResourceLabelEvent < ResourceEvent
end
def banzai_render_context(field)
- super.merge(pipeline: 'label', only_path: true)
+ super.merge(pipeline: :label, only_path: true)
end
def refresh_invalid_reference
diff --git a/app/models/resource_milestone_event.rb b/app/models/resource_milestone_event.rb
index b97c02f1713..a40af22061e 100644
--- a/app/models/resource_milestone_event.rb
+++ b/app/models/resource_milestone_event.rb
@@ -13,9 +13,9 @@ class ResourceMilestoneEvent < ResourceEvent
validate :exactly_one_issuable
enum action: {
- add: 1,
- remove: 2
- }
+ add: 1,
+ remove: 2
+ }
# state is used for issue and merge request states.
enum state: Issue.available_states.merge(MergeRequest.available_states)
diff --git a/app/models/route.rb b/app/models/route.rb
index 91ea2966013..63a0461807b 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -2,9 +2,9 @@
class Route < ApplicationRecord
include CaseSensitivity
+ include Gitlab::SQL::Pattern
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
-
validates :source, presence: true
validates :path,
@@ -19,6 +19,8 @@ class Route < ApplicationRecord
after_update :rename_descendants
scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") }
+ scope :for_routable, -> (routable) { where(source: routable) }
+ scope :sort_by_path_length, -> { order('LENGTH(routes.path)', :path) }
def rename_descendants
return unless saved_change_to_path? || saved_change_to_name?
diff --git a/app/models/terraform/state.rb b/app/models/terraform/state.rb
index 8ca4ee9239a..c4e047ff9d1 100644
--- a/app/models/terraform/state.rb
+++ b/app/models/terraform/state.rb
@@ -2,14 +2,25 @@
module Terraform
class State < ApplicationRecord
+ DEFAULT = '{"version":1}'.freeze
+ HEX_REGEXP = %r{\A\h+\z}.freeze
+ UUID_LENGTH = 32
+
belongs_to :project
+ belongs_to :locked_by_user, class_name: 'User'
validates :project_id, presence: true
+ validates :uuid, presence: true, uniqueness: true, length: { is: UUID_LENGTH },
+ format: { with: HEX_REGEXP, message: 'only allows hex characters' }
+
+ default_value_for(:uuid, allows_nil: false) { SecureRandom.hex(UUID_LENGTH / 2) }
after_save :update_file_store, if: :saved_change_to_file?
mount_uploader :file, StateUploader
+ default_value_for(:file) { CarrierWaveStringFile.new(DEFAULT) }
+
def update_file_store
# The file.object_store is set during `uploader.store!`
# which happens after object is inserted/updated
@@ -19,5 +30,9 @@ module Terraform
def file_store
super || StateUploader.default_store
end
+
+ def locked?
+ self.lock_xid.present?
+ end
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 42972477d97..1b087da3a2f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -337,7 +337,8 @@ class User < ApplicationRecord
scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) }
scope :with_public_profile, -> { where(private_profile: false) }
scope :bots, -> { where(user_type: UserTypeEnums.bots.values) }
- scope :not_bots, -> { humans.or(where.not(user_type: UserTypeEnums.bots.values)) }
+ scope :bots_without_project_bot, -> { bots.where.not(user_type: UserTypeEnums.bots[:project_bot]) }
+ scope :with_project_bots, -> { humans.or(where.not(user_type: UserTypeEnums.bots.except(:project_bot).values)) }
scope :humans, -> { where(user_type: nil) }
scope :with_expiring_and_not_notified_personal_access_tokens, ->(at) do
@@ -657,8 +658,10 @@ class User < ApplicationRecord
UserTypeEnums.bots.has_key?(user_type)
end
+ # The explicit check for project_bot will be removed with Bot Categorization
+ # Ref: https://gitlab.com/gitlab-org/gitlab/-/issues/213945
def internal?
- ghost? || bot?
+ ghost? || (bot? && !project_bot?)
end
# We are transitioning from ghost boolean column to user_type
@@ -668,12 +671,16 @@ class User < ApplicationRecord
ghost
end
+ # The explicit check for project_bot will be removed with Bot Categorization
+ # Ref: https://gitlab.com/gitlab-org/gitlab/-/issues/213945
def self.internal
- where(ghost: true).or(bots)
+ where(ghost: true).or(bots_without_project_bot)
end
+ # The explicit check for project_bot will be removed with Bot Categorization
+ # Ref: https://gitlab.com/gitlab-org/gitlab/-/issues/213945
def self.non_internal
- without_ghosts.not_bots
+ without_ghosts.with_project_bots
end
#
@@ -1720,7 +1727,7 @@ class User < ApplicationRecord
# override, from Devise::Validatable
def password_required?
- return false if internal?
+ return false if internal? || project_bot?
super
end
diff --git a/app/models/user_type_enums.rb b/app/models/user_type_enums.rb
index 795cc4b2889..cb5aac89ed3 100644
--- a/app/models/user_type_enums.rb
+++ b/app/models/user_type_enums.rb
@@ -6,7 +6,7 @@ module UserTypeEnums
end
def self.bots
- @bots ||= { alert_bot: 2 }.with_indifferent_access
+ @bots ||= { alert_bot: 2, project_bot: 6 }.with_indifferent_access
end
end