summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
authorIgor <idrozdov@gitlab.com>2019-08-05 15:06:02 +0000
committerIgor <idrozdov@gitlab.com>2019-08-05 15:06:02 +0000
commit7efb062c3c3c7b44113d0dc0fe78fc9b8e95bd7c (patch)
treea12bde9bbeffcc0c365d3a29339d0389dcefdd8f /app/models
parent2bd1320f86b8cfd5d60199c5f7f0caa1cc2aa66b (diff)
parent3dfc89ade452ad7f0185653b30ed1d4bb2544fb0 (diff)
downloadgitlab-ce-id-test-codeowners.tar.gz
Merge branch 'master' into 'id-test-codeowners'id-test-codeowners
# Conflicts: # .gitlab/CODEOWNERS
Diffstat (limited to 'app/models')
-rw-r--r--app/models/active_session.rb20
-rw-r--r--app/models/application_setting.rb5
-rw-r--r--app/models/application_setting_implementation.rb75
-rw-r--r--app/models/ci/build.rb29
-rw-r--r--app/models/ci/build_need.rb14
-rw-r--r--app/models/ci/job_artifact.rb4
-rw-r--r--app/models/ci/job_variable.rb14
-rw-r--r--app/models/ci/pipeline.rb21
-rw-r--r--app/models/ci/pipeline_schedule.rb3
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/ci/trigger.rb3
-rw-r--r--app/models/clusters/applications/cert_manager.rb38
-rw-r--r--app/models/clusters/applications/helm.rb28
-rw-r--r--app/models/clusters/applications/knative.rb60
-rw-r--r--app/models/clusters/applications/prometheus.rb19
-rw-r--r--app/models/clusters/applications/runner.rb17
-rw-r--r--app/models/clusters/cluster.rb16
-rw-r--r--app/models/clusters/concerns/application_core.rb10
-rw-r--r--app/models/clusters/concerns/application_status.rb24
-rw-r--r--app/models/commit.rb2
-rw-r--r--app/models/commit_status.rb12
-rw-r--r--app/models/concerns/case_sensitivity.rb4
-rw-r--r--app/models/concerns/ci/metadatable.rb1
-rw-r--r--app/models/concerns/deployment_platform.rb23
-rw-r--r--app/models/concerns/descendant.rb11
-rw-r--r--app/models/concerns/diff_positionable_note.rb8
-rw-r--r--app/models/concerns/group_descendant.rb4
-rw-r--r--app/models/concerns/issuable.rb7
-rw-r--r--app/models/concerns/new_has_variable.rb14
-rw-r--r--app/models/concerns/project_api_compatibility.rb6
-rw-r--r--app/models/concerns/protected_ref.rb2
-rw-r--r--app/models/concerns/relative_positioning.rb141
-rw-r--r--app/models/concerns/routable.rb43
-rw-r--r--app/models/concerns/stepable.rb35
-rw-r--r--app/models/concerns/taskable.rb3
-rw-r--r--app/models/concerns/token_authenticatable.rb4
-rw-r--r--app/models/concerns/update_project_statistics.rb7
-rw-r--r--app/models/container_repository.rb11
-rw-r--r--app/models/cycle_analytics/group_level.rb29
-rw-r--r--app/models/cycle_analytics/level_base.rb (renamed from app/models/cycle_analytics/base.rb)6
-rw-r--r--app/models/cycle_analytics/project_level.rb5
-rw-r--r--app/models/dashboard_group_milestone.rb3
-rw-r--r--app/models/deployment.rb11
-rw-r--r--app/models/deployment_metrics.rb13
-rw-r--r--app/models/diff_note.rb7
-rw-r--r--app/models/environment.rb49
-rw-r--r--app/models/group.rb13
-rw-r--r--app/models/hooks/system_hook.rb4
-rw-r--r--app/models/hooks/web_hook.rb6
-rw-r--r--app/models/issue.rb11
-rw-r--r--app/models/label.rb6
-rw-r--r--app/models/members/group_member.rb2
-rw-r--r--app/models/merge_request.rb16
-rw-r--r--app/models/milestone.rb31
-rw-r--r--app/models/namespace.rb5
-rw-r--r--app/models/namespace/aggregation_schedule.rb14
-rw-r--r--app/models/note.rb6
-rw-r--r--app/models/pages_domain.rb2
-rw-r--r--app/models/personal_access_token.rb1
-rw-r--r--app/models/project.rb97
-rw-r--r--app/models/project_auto_devops.rb6
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/models/project_services/chat_message/pipeline_message.rb225
-rw-r--r--app/models/project_services/jira_service.rb10
-rw-r--r--app/models/project_statistics.rb6
-rw-r--r--app/models/redirect_route.rb6
-rw-r--r--app/models/remote_mirror.rb4
-rw-r--r--app/models/repository.rb3
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/user.rb5
-rw-r--r--app/models/user_preference.rb2
71 files changed, 878 insertions, 470 deletions
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
index f355b02c428..fdd210f0fba 100644
--- a/app/models/active_session.rb
+++ b/app/models/active_session.rb
@@ -3,6 +3,8 @@
class ActiveSession
include ActiveModel::Model
+ SESSION_BATCH_SIZE = 200
+
attr_accessor :created_at, :updated_at,
:session_id, :ip_address,
:browser, :os, :device_name, :device_type,
@@ -91,12 +93,12 @@ class ActiveSession
end
def self.list_sessions(user)
- sessions_from_ids(session_ids_for_user(user))
+ sessions_from_ids(session_ids_for_user(user.id))
end
- def self.session_ids_for_user(user)
+ def self.session_ids_for_user(user_id)
Gitlab::Redis::SharedState.with do |redis|
- redis.smembers(lookup_key_name(user.id))
+ redis.smembers(lookup_key_name(user_id))
end
end
@@ -106,10 +108,12 @@ class ActiveSession
Gitlab::Redis::SharedState.with do |redis|
session_keys = session_ids.map { |session_id| "#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}" }
- redis.mget(session_keys).compact.map do |raw_session|
- # rubocop:disable Security/MarshalLoad
- Marshal.load(raw_session)
- # rubocop:enable Security/MarshalLoad
+ session_keys.each_slice(SESSION_BATCH_SIZE).flat_map do |session_keys_batch|
+ redis.mget(session_keys_batch).compact.map do |raw_session|
+ # rubocop:disable Security/MarshalLoad
+ Marshal.load(raw_session)
+ # rubocop:enable Security/MarshalLoad
+ end
end
end
end
@@ -125,7 +129,7 @@ class ActiveSession
end
def self.cleaned_up_lookup_entries(redis, user)
- session_ids = session_ids_for_user(user)
+ session_ids = session_ids_for_user(user.id)
entries = raw_active_session_entries(session_ids, user.id)
# remove expired keys.
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 8e558487c1c..9dbcef8abaa 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -41,6 +41,11 @@ class ApplicationSetting < ApplicationRecord
validates :uuid, presence: true
+ validates :outbound_local_requests_whitelist,
+ length: { maximum: 1_000, message: N_('is too long (maximum is 1000 entries)') },
+ allow_nil: false,
+ qualified_domain_array: true
+
validates :session_expire_delay,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index df4caed175d..b7a4d7aa803 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -2,6 +2,7 @@
module ApplicationSettingImplementation
extend ActiveSupport::Concern
+ include Gitlab::Utils::StrongMemoize
DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
| # or
@@ -20,7 +21,8 @@ module ApplicationSettingImplementation
{
after_sign_up_text: nil,
akismet_enabled: false,
- allow_local_requests_from_hooks_and_services: false,
+ allow_local_requests_from_web_hooks_and_services: false,
+ allow_local_requests_from_system_hooks: true,
dns_rebinding_protection_enabled: true,
authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
container_registry_token_expire_delay: 5,
@@ -96,7 +98,9 @@ module ApplicationSettingImplementation
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
commit_email_hostname: default_commit_email_hostname,
protected_ci_variables: false,
- local_markdown_version: 0
+ local_markdown_version: 0,
+ outbound_local_requests_whitelist: [],
+ raw_blob_request_limit: 300
}
end
@@ -131,31 +135,65 @@ module ApplicationSettingImplementation
end
def domain_whitelist_raw
- self.domain_whitelist&.join("\n")
+ array_to_string(self.domain_whitelist)
end
def domain_blacklist_raw
- self.domain_blacklist&.join("\n")
+ array_to_string(self.domain_blacklist)
end
def domain_whitelist_raw=(values)
- self.domain_whitelist = []
- self.domain_whitelist = values.split(DOMAIN_LIST_SEPARATOR)
- self.domain_whitelist.reject! { |d| d.empty? }
- self.domain_whitelist
+ self.domain_whitelist = domain_strings_to_array(values)
end
def domain_blacklist_raw=(values)
- self.domain_blacklist = []
- self.domain_blacklist = values.split(DOMAIN_LIST_SEPARATOR)
- self.domain_blacklist.reject! { |d| d.empty? }
- self.domain_blacklist
+ self.domain_blacklist = domain_strings_to_array(values)
end
def domain_blacklist_file=(file)
self.domain_blacklist_raw = file.read
end
+ def outbound_local_requests_whitelist_raw
+ array_to_string(self.outbound_local_requests_whitelist)
+ end
+
+ def outbound_local_requests_whitelist_raw=(values)
+ clear_memoization(:outbound_local_requests_whitelist_arrays)
+
+ self.outbound_local_requests_whitelist = domain_strings_to_array(values)
+ end
+
+ def add_to_outbound_local_requests_whitelist(values_array)
+ clear_memoization(:outbound_local_requests_whitelist_arrays)
+
+ self.outbound_local_requests_whitelist ||= []
+ self.outbound_local_requests_whitelist += values_array
+
+ self.outbound_local_requests_whitelist.uniq!
+ end
+
+ def outbound_local_requests_whitelist_arrays
+ strong_memoize(:outbound_local_requests_whitelist_arrays) do
+ next [[], []] unless self.outbound_local_requests_whitelist
+
+ ip_whitelist = []
+ domain_whitelist = []
+
+ self.outbound_local_requests_whitelist.each do |str|
+ ip_obj = Gitlab::Utils.string_to_ip_object(str)
+
+ if ip_obj
+ ip_whitelist << ip_obj
+ else
+ domain_whitelist << str
+ end
+ end
+
+ [ip_whitelist, domain_whitelist]
+ end
+ end
+
def repository_storages
Array(read_attribute(:repository_storages))
end
@@ -255,6 +293,19 @@ module ApplicationSettingImplementation
private
+ def array_to_string(arr)
+ arr&.join("\n")
+ end
+
+ def domain_strings_to_array(values)
+ return [] unless values
+
+ values
+ .split(DOMAIN_LIST_SEPARATOR)
+ .reject(&:empty?)
+ .uniq
+ end
+
def ensure_uuid!
return if uuid?
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 635fcc86166..ac88d9714ac 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -38,8 +38,10 @@ module Ci
has_one :deployment, as: :deployable, class_name: 'Deployment'
has_many :trace_sections, class_name: 'Ci::BuildTraceSection'
has_many :trace_chunks, class_name: 'Ci::BuildTraceChunk', foreign_key: :build_id
+ has_many :needs, class_name: 'Ci::BuildNeed', foreign_key: :build_id, inverse_of: :build
has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy, inverse_of: :job # rubocop:disable Cop/ActiveRecordDependent
+ has_many :job_variables, class_name: 'Ci::JobVariable', foreign_key: :job_id
Ci::JobArtifact.file_types.each do |key, value|
has_one :"job_artifacts_#{key}", -> { where(file_type: value) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
@@ -48,6 +50,8 @@ module Ci
has_one :runner_session, class_name: 'Ci::BuildRunnerSession', validate: true, inverse_of: :build
accepts_nested_attributes_for :runner_session
+ accepts_nested_attributes_for :job_variables
+ accepts_nested_attributes_for :needs
delegate :url, to: :runner_session, prefix: true, allow_nil: true
delegate :terminal_specification, to: :runner_session, allow_nil: true
@@ -331,10 +335,10 @@ module Ci
end
# rubocop: disable CodeReuse/ServiceClass
- def play(current_user)
+ def play(current_user, job_variables_attributes = nil)
Ci::PlayBuildService
.new(project, current_user)
- .execute(self)
+ .execute(self, job_variables_attributes)
end
# rubocop: enable CodeReuse/ServiceClass
@@ -432,6 +436,7 @@ module Ci
Gitlab::Ci::Variables::Collection.new
.concat(persisted_variables)
.concat(scoped_variables)
+ .concat(job_variables)
.concat(persisted_environment_variables)
.to_runner_variables
end
@@ -531,6 +536,14 @@ module Ci
trace.exist?
end
+ def has_live_trace?
+ trace.live_trace_exist?
+ end
+
+ def has_archived_trace?
+ trace.archived_trace_exist?
+ end
+
def artifacts_file
job_artifacts_archive&.file
end
@@ -702,11 +715,17 @@ module Ci
depended_jobs = depends_on_builds
- return depended_jobs unless options[:dependencies].present?
+ # find all jobs that are needed
+ if Feature.enabled?(:ci_dag_support, project) && needs.exists?
+ depended_jobs = depended_jobs.where(name: needs.select(:name))
+ end
- depended_jobs.select do |job|
- options[:dependencies].include?(job.name)
+ # find all jobs that are dependent on
+ if options[:dependencies].present?
+ depended_jobs = depended_jobs.where(name: options[:dependencies])
end
+
+ depended_jobs
end
def empty_dependencies?
diff --git a/app/models/ci/build_need.rb b/app/models/ci/build_need.rb
new file mode 100644
index 00000000000..6531dfd332f
--- /dev/null
+++ b/app/models/ci/build_need.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Ci
+ class BuildNeed < ApplicationRecord
+ extend Gitlab::Ci::Model
+
+ belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id, inverse_of: :needs
+
+ validates :build, presence: true
+ validates :name, presence: true, length: { maximum: 128 }
+
+ scope :scoped_build, -> { where('ci_builds.id=ci_build_needs.build_id') }
+ end
+end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index f80e98e5bca..e132cb045e2 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -176,6 +176,10 @@ module Ci
end
end
+ def self.archived_trace_exists_for?(job_id)
+ where(job_id: job_id).trace.take&.file&.file&.exists?
+ end
+
private
def file_format_adapter_class
diff --git a/app/models/ci/job_variable.rb b/app/models/ci/job_variable.rb
new file mode 100644
index 00000000000..862a0bc1299
--- /dev/null
+++ b/app/models/ci/job_variable.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Ci
+ class JobVariable < ApplicationRecord
+ extend Gitlab::Ci::Model
+ include NewHasVariable
+
+ belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
+
+ alias_attribute :secret_value, :value
+
+ validates :key, uniqueness: { scope: :job_id }
+ end
+end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 2262282e647..3b28eb246db 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -229,15 +229,15 @@ module Ci
#
# ref - The name (or names) of the branch(es)/tag(s) to limit the list of
# pipelines to.
+ # sha - The commit SHA (or mutliple SHAs) to limit the list of pipelines to.
# limit - This limits a backlog search, default to 100.
- def self.newest_first(ref: nil, limit: 100)
+ def self.newest_first(ref: nil, sha: nil, limit: 100)
relation = order(id: :desc)
relation = relation.where(ref: ref) if ref
+ relation = relation.where(sha: sha) if sha
if limit
ids = relation.limit(limit).select(:id)
- # MySQL does not support limit in subquery
- ids = ids.pluck(:id) if Gitlab::Database.mysql?
relation = relation.where(id: ids)
end
@@ -248,10 +248,14 @@ module Ci
newest_first(ref: ref).pluck(:status).first
end
- def self.latest_successful_for(ref)
+ def self.latest_successful_for_ref(ref)
newest_first(ref: ref).success.take
end
+ def self.latest_successful_for_sha(sha)
+ newest_first(sha: sha).success.take
+ end
+
def self.latest_successful_for_refs(refs)
relation = newest_first(ref: refs).success
@@ -500,8 +504,9 @@ module Ci
return [] unless config_processor
strong_memoize(:stage_seeds) do
- seeds = config_processor.stages_attributes.map do |attributes|
- Gitlab::Ci::Pipeline::Seed::Stage.new(self, attributes)
+ seeds = config_processor.stages_attributes.inject([]) do |previous_stages, attributes|
+ seed = Gitlab::Ci::Pipeline::Seed::Stage.new(self, attributes, previous_stages)
+ previous_stages + [seed]
end
seeds.select(&:included?)
@@ -607,8 +612,8 @@ module Ci
end
# rubocop: disable CodeReuse/ServiceClass
- def process!
- Ci::ProcessPipelineService.new(project, user).execute(self)
+ def process!(trigger_build_ids = nil)
+ Ci::ProcessPipelineService.new(project, user).execute(self, trigger_build_ids)
end
# rubocop: enable CodeReuse/ServiceClass
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index ba8cea0cea9..42d4e86fe8d 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -4,11 +4,8 @@ module Ci
class PipelineSchedule < ApplicationRecord
extend Gitlab::Ci::Model
include Importable
- include IgnorableColumn
include StripAttribute
- ignore_column :deleted_at
-
belongs_to :project
belongs_to :owner, class_name: 'User'
has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline'
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 07d00503861..43ff874ac23 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -264,7 +264,7 @@ module Ci
private
def cleanup_runner_queue
- Gitlab::Redis::Queues.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
redis.del(runner_queue_key)
end
end
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index dc9965ce08c..8792c5cf98b 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -3,11 +3,8 @@
module Ci
class Trigger < ApplicationRecord
extend Gitlab::Ci::Model
- include IgnorableColumn
include Presentable
- ignore_column :deleted_at
-
belongs_to :project
belongs_to :owner, class_name: "User"
diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb
index d6a7d1d2bdd..2fc1b67dfd2 100644
--- a/app/models/clusters/applications/cert_manager.rb
+++ b/app/models/clusters/applications/cert_manager.rb
@@ -24,12 +24,6 @@ module Clusters
'stable/cert-manager'
end
- # We will implement this in future MRs.
- # Need to reverse postinstall step
- def allowed_to_uninstall?
- false
- end
-
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: 'certmanager',
@@ -41,10 +35,40 @@ module Clusters
)
end
+ def uninstall_command
+ Gitlab::Kubernetes::Helm::DeleteCommand.new(
+ name: 'certmanager',
+ rbac: cluster.platform_kubernetes_rbac?,
+ files: files,
+ postdelete: post_delete_script
+ )
+ end
+
private
def post_install_script
- ["/usr/bin/kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml"]
+ ["kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml"]
+ end
+
+ def post_delete_script
+ [
+ delete_private_key,
+ delete_crd('certificates.certmanager.k8s.io'),
+ delete_crd('clusterissuers.certmanager.k8s.io'),
+ delete_crd('issuers.certmanager.k8s.io')
+ ].compact
+ end
+
+ def private_key_name
+ @private_key_name ||= cluster_issuer_content.dig('spec', 'acme', 'privateKeySecretRef', 'name')
+ end
+
+ def delete_private_key
+ "kubectl delete secret -n #{Gitlab::Kubernetes::Helm::NAMESPACE} #{private_key_name} --ignore-not-found" if private_key_name.present?
+ end
+
+ def delete_crd(definition)
+ "kubectl delete crd #{definition} --ignore-not-found"
end
def cluster_issuer_file
diff --git a/app/models/clusters/applications/helm.rb b/app/models/clusters/applications/helm.rb
index a83d06c4b00..3a175fec148 100644
--- a/app/models/clusters/applications/helm.rb
+++ b/app/models/clusters/applications/helm.rb
@@ -14,6 +14,7 @@ module Clusters
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
+ include ::Gitlab::Utils::StrongMemoize
default_value_for :version, Gitlab::Kubernetes::Helm::HELM_VERSION
@@ -29,11 +30,22 @@ module Clusters
self.status = 'installable' if cluster&.platform_kubernetes_active?
end
- # We will implement this in future MRs.
- # Basically we need to check all other applications are not installed
- # first.
+ # It can only be uninstalled if there are no other applications installed
+ # or with intermitent installation statuses in the database.
def allowed_to_uninstall?
- false
+ strong_memoize(:allowed_to_uninstall) do
+ applications = nil
+
+ Clusters::Cluster::APPLICATIONS.each do |application_name, klass|
+ next if application_name == 'helm'
+
+ extra_apps = Clusters::Applications::Helm.where('EXISTS (?)', klass.select(1).where(cluster_id: cluster_id))
+
+ applications = applications.present? ? applications.or(extra_apps) : extra_apps
+ end
+
+ !applications.exists?
+ end
end
def install_command
@@ -44,6 +56,14 @@ module Clusters
)
end
+ def uninstall_command
+ Gitlab::Kubernetes::Helm::ResetCommand.new(
+ name: name,
+ files: files,
+ rbac: cluster.platform_kubernetes_rbac?
+ )
+ end
+
def has_ssl?
ca_key.present? && ca_cert.present?
end
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index 5df4812bd25..5eae23659ae 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -7,6 +7,7 @@ module Clusters
REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze
METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'.freeze
FETCH_IP_ADDRESS_DELAY = 30.seconds
+ API_RESOURCES_PATH = 'config/knative/api_resources.yml'
self.table_name = 'clusters_applications_knative'
@@ -46,12 +47,6 @@ module Clusters
{ "domain" => hostname }.to_yaml
end
- # Handled in a new issue:
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/59369
- def allowed_to_uninstall?
- false
- end
-
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
@@ -76,10 +71,61 @@ module Clusters
cluster.kubeclient.get_service('istio-ingressgateway', 'istio-system')
end
+ def uninstall_command
+ Gitlab::Kubernetes::Helm::DeleteCommand.new(
+ name: name,
+ rbac: cluster.platform_kubernetes_rbac?,
+ files: files,
+ predelete: delete_knative_services_and_metrics,
+ postdelete: delete_knative_istio_leftovers
+ )
+ end
+
private
+ def delete_knative_services_and_metrics
+ delete_knative_services + delete_knative_istio_metrics
+ end
+
+ def delete_knative_services
+ cluster.kubernetes_namespaces.map do |kubernetes_namespace|
+ "kubectl delete ksvc --all -n #{kubernetes_namespace.namespace}"
+ end
+ end
+
+ def delete_knative_istio_leftovers
+ delete_knative_namespaces + delete_knative_and_istio_crds
+ end
+
+ def delete_knative_namespaces
+ [
+ "kubectl delete --ignore-not-found ns knative-serving",
+ "kubectl delete --ignore-not-found ns knative-build"
+ ]
+ end
+
+ def delete_knative_and_istio_crds
+ api_resources.map do |crd|
+ "kubectl delete --ignore-not-found crd #{crd}"
+ end
+ end
+
+ # returns an array of CRDs to be postdelete since helm does not
+ # manage the CRDs it creates.
+ def api_resources
+ @api_resources ||= YAML.safe_load(File.read(Rails.root.join(API_RESOURCES_PATH)))
+ end
+
def install_knative_metrics
- ["kubectl apply -f #{METRICS_CONFIG}"] if cluster.application_prometheus_available?
+ return [] unless cluster.application_prometheus_available?
+
+ ["kubectl apply -f #{METRICS_CONFIG}"]
+ end
+
+ def delete_knative_istio_metrics
+ return [] unless cluster.application_prometheus_available?
+
+ ["kubectl delete --ignore-not-found -f #{METRICS_CONFIG}"]
end
def verify_cluster?
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 805c8a73f8c..5eb535cab58 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -59,6 +59,15 @@ module Clusters
)
end
+ def uninstall_command
+ Gitlab::Kubernetes::Helm::DeleteCommand.new(
+ name: name,
+ rbac: cluster.platform_kubernetes_rbac?,
+ files: files,
+ predelete: delete_knative_istio_metrics
+ )
+ end
+
# Returns a copy of files where the values of 'values.yaml'
# are replaced by the argument.
#
@@ -95,7 +104,15 @@ module Clusters
end
def install_knative_metrics
- ["kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}"] if cluster.application_knative_available?
+ return [] unless cluster.application_knative_available?
+
+ ["kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}"]
+ end
+
+ def delete_knative_istio_metrics
+ return [] unless cluster.application_knative_available?
+
+ ["kubectl delete -f #{Clusters::Applications::Knative::METRICS_CONFIG}"]
end
end
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index f0256ff4d41..6533b7a186e 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.6.0'.freeze
+ VERSION = '0.7.0'.freeze
self.table_name = 'clusters_applications_runners'
@@ -29,13 +29,6 @@ module Clusters
content_values.to_yaml
end
- # Need to investigate if pipelines run by this runner will stop upon the
- # executor pod stopping
- # I.e.run a pipeline, and uninstall runner while pipeline is running
- def allowed_to_uninstall?
- false
- end
-
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
@@ -47,6 +40,14 @@ module Clusters
)
end
+ def prepare_uninstall
+ runner&.update!(active: false)
+ end
+
+ def post_uninstall
+ runner.destroy!
+ end
+
private
def ensure_runner
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 8c044c86c47..8bb44b0ce40 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -100,12 +100,6 @@ module Clusters
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
- scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do
- subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.cluster_id = clusters.id')
-
- where('NOT EXISTS (?)', subquery)
- end
-
scope :with_knative_installed, -> { joins(:application_knative).merge(Clusters::Applications::Knative.available) }
scope :preload_knative, -> {
@@ -161,16 +155,6 @@ module Clusters
return platform_kubernetes if kubernetes?
end
- def all_projects
- if project_type?
- projects
- elsif group_type?
- first_group.all_projects
- else
- Project.none
- end
- end
-
def first_project
strong_memoize(:first_project) do
projects.first
diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb
index 4514498b84b..803a65726d3 100644
--- a/app/models/clusters/concerns/application_core.rb
+++ b/app/models/clusters/concerns/application_core.rb
@@ -46,6 +46,16 @@ module Clusters
command.version = version
end
end
+
+ def prepare_uninstall
+ # Override if your application needs any action before
+ # being uninstalled by Helm
+ end
+
+ def post_uninstall
+ # Override if your application needs any action after
+ # being uninstalled by Helm
+ end
end
end
end
diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb
index 54a3dda6d75..342d766f723 100644
--- a/app/models/clusters/concerns/application_status.rb
+++ b/app/models/clusters/concerns/application_status.rb
@@ -59,29 +59,33 @@ module Clusters
transition [:scheduled] => :uninstalling
end
- before_transition any => [:scheduled] do |app_status, _|
- app_status.status_reason = nil
+ before_transition any => [:scheduled] do |application, _|
+ application.status_reason = nil
end
- before_transition any => [:errored] do |app_status, transition|
+ before_transition any => [:errored] do |application, transition|
status_reason = transition.args.first
- app_status.status_reason = status_reason if status_reason
+ application.status_reason = status_reason if status_reason
end
- before_transition any => [:updating] do |app_status, _|
- app_status.status_reason = nil
+ before_transition any => [:updating] do |application, _|
+ application.status_reason = nil
end
- before_transition any => [:update_errored, :uninstall_errored] do |app_status, transition|
+ before_transition any => [:update_errored, :uninstall_errored] do |application, transition|
status_reason = transition.args.first
- app_status.status_reason = status_reason if status_reason
+ application.status_reason = status_reason if status_reason
end
- before_transition any => [:installed, :updated] do |app_status, _|
+ before_transition any => [:installed, :updated] do |application, _|
# When installing any application we are also performing an update
# of tiller (see Gitlab::Kubernetes::Helm::ClientCommand) so
# therefore we need to reflect that in the database.
- app_status.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION)
+ application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION)
+ end
+
+ after_transition any => [:uninstalling], :use_transactions => false do |application, _|
+ application.prepare_uninstall
end
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index be37fa2e76f..0889ce7e287 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -346,7 +346,7 @@ class Commit
if commits_in_merge_request.present?
message_body << ""
- commits_in_merge_request.reverse.each do |commit_in_merge|
+ commits_in_merge_request.reverse_each do |commit_in_merge|
message_body << "#{commit_in_merge.short_id} #{commit_in_merge.title}"
end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index be6f3e9c5b0..a88cac6b8e6 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -43,6 +43,16 @@ class CommitStatus < ApplicationRecord
scope :after_stage, -> (index) { where('stage_idx > ?', index) }
scope :processables, -> { where(type: %w[Ci::Build Ci::Bridge]) }
+ scope :with_needs, -> (names = nil) do
+ needs = Ci::BuildNeed.scoped_build.select(1)
+ needs = needs.where(name: names) if names
+ where('EXISTS (?)', needs).preload(:needs)
+ end
+
+ scope :without_needs, -> do
+ where('NOT EXISTS (?)', Ci::BuildNeed.scoped_build.select(1))
+ end
+
# We use `CommitStatusEnums.failure_reasons` here so that EE can more easily
# extend this `Hash` with new values.
enum_with_nil failure_reason: ::CommitStatusEnums.failure_reasons
@@ -116,7 +126,7 @@ class CommitStatus < ApplicationRecord
commit_status.run_after_commit do
if pipeline_id
if complete? || manual?
- PipelineProcessWorker.perform_async(pipeline_id)
+ PipelineProcessWorker.perform_async(pipeline_id, [id])
else
PipelineUpdateWorker.perform_async(pipeline_id)
end
diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
index c93b6589ee7..abddbf1c7e3 100644
--- a/app/models/concerns/case_sensitivity.rb
+++ b/app/models/concerns/case_sensitivity.rb
@@ -40,14 +40,10 @@ module CaseSensitivity
end
def lower_value(value)
- return value if Gitlab::Database.mysql?
-
Arel::Nodes::NamedFunction.new('LOWER', [Arel::Nodes.build_quoted(value)])
end
def lower_column(column)
- return column if Gitlab::Database.mysql?
-
column.lower
end
end
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index 9eed9492b37..304cc71e9dc 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -29,6 +29,7 @@ module Ci
def degenerate!
self.class.transaction do
self.update!(options: nil, yaml_variables: nil)
+ self.needs.all.delete_all
self.metadata&.destroy
end
end
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 8f28c897eb6..e1a8725e728 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -12,19 +12,10 @@ module DeploymentPlatform
private
def find_deployment_platform(environment)
- find_platform_kubernetes(environment) ||
+ find_platform_kubernetes_with_cte(environment) ||
find_instance_cluster_platform_kubernetes(environment: environment)
end
- def find_platform_kubernetes(environment)
- if Feature.enabled?(:clusters_cte)
- find_platform_kubernetes_with_cte(environment)
- else
- find_cluster_platform_kubernetes(environment: environment) ||
- find_group_cluster_platform_kubernetes(environment: environment)
- end
- end
-
# EE would override this and utilize environment argument
def find_platform_kubernetes_with_cte(_environment)
Clusters::ClustersHierarchy.new(self).base_and_ancestors
@@ -33,18 +24,6 @@ module DeploymentPlatform
end
# EE would override this and utilize environment argument
- def find_cluster_platform_kubernetes(environment: nil)
- clusters.enabled.default_environment
- .last&.platform_kubernetes
- end
-
- # EE would override this and utilize environment argument
- def find_group_cluster_platform_kubernetes(environment: nil)
- Clusters::Cluster.enabled.default_environment.ancestor_clusters_for_clusterable(self)
- .first&.platform_kubernetes
- end
-
- # EE would override this and utilize environment argument
def find_instance_cluster_platform_kubernetes(environment: nil)
Clusters::Instance.new.clusters.enabled.default_environment
.first&.platform_kubernetes
diff --git a/app/models/concerns/descendant.rb b/app/models/concerns/descendant.rb
deleted file mode 100644
index 4c436522122..00000000000
--- a/app/models/concerns/descendant.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module Descendant
- extend ActiveSupport::Concern
-
- class_methods do
- def supports_nested_objects?
- Gitlab::Database.postgresql?
- end
- end
-end
diff --git a/app/models/concerns/diff_positionable_note.rb b/app/models/concerns/diff_positionable_note.rb
index 2d09eff0111..195d9e107c5 100644
--- a/app/models/concerns/diff_positionable_note.rb
+++ b/app/models/concerns/diff_positionable_note.rb
@@ -10,6 +10,8 @@ module DiffPositionableNote
serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
+
+ validate :diff_refs_match_commit, if: :for_commit?
end
%i(original_position position change_position).each do |meth|
@@ -71,4 +73,10 @@ module DiffPositionableNote
self.position = result[:position]
end
end
+
+ def diff_refs_match_commit
+ return if self.original_position.diff_refs == commit&.diff_refs
+
+ errors.add(:commit_id, 'does not match the diff refs')
+ end
end
diff --git a/app/models/concerns/group_descendant.rb b/app/models/concerns/group_descendant.rb
index cfffd845e43..ed14b73ac1b 100644
--- a/app/models/concerns/group_descendant.rb
+++ b/app/models/concerns/group_descendant.rb
@@ -42,7 +42,7 @@ module GroupDescendant
parent = child.parent
exception = ArgumentError.new <<~MSG
- parent: [GroupDescendant: #{parent.inspect}] was not preloaded for [#{child.inspect}]")
+ Parent was not preloaded for child when rendering group hierarchy.
This error is not user facing, but causes a +1 query.
MSG
extras = {
@@ -50,7 +50,7 @@ module GroupDescendant
child: child.inspect,
preloaded: preloaded.map(&:full_path)
}
- issue_url = 'https://gitlab.com/gitlab-org/gitlab-ce/issues/40785'
+ issue_url = 'https://gitlab.com/gitlab-org/gitlab-ce/issues/49404'
Gitlab::Sentry.track_exception(exception, issue_url: issue_url, extra: extras)
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 952de92cae1..e60b6497cb7 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -427,4 +427,11 @@ module Issuable
def wipless_title_changed(old_title)
old_title != title
end
+
+ ##
+ # Overridden on EE module
+ #
+ def supports_milestone?
+ respond_to?(:milestone_id)
+ end
end
diff --git a/app/models/concerns/new_has_variable.rb b/app/models/concerns/new_has_variable.rb
new file mode 100644
index 00000000000..429bf496872
--- /dev/null
+++ b/app/models/concerns/new_has_variable.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module NewHasVariable
+ extend ActiveSupport::Concern
+ include HasVariable
+
+ included do
+ attr_encrypted :value,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm',
+ key: Settings.attr_encrypted_db_key_base_32,
+ insecure_mode: false
+ end
+end
diff --git a/app/models/concerns/project_api_compatibility.rb b/app/models/concerns/project_api_compatibility.rb
index cb00efb06df..631b2a11e9a 100644
--- a/app/models/concerns/project_api_compatibility.rb
+++ b/app/models/concerns/project_api_compatibility.rb
@@ -9,12 +9,10 @@ module ProjectAPICompatibility
end
def auto_devops_enabled=(value)
- self.build_auto_devops if self.auto_devops&.enabled.nil?
- self.auto_devops.update! enabled: value
+ (auto_devops || build_auto_devops).enabled = value
end
def auto_devops_deploy_strategy=(value)
- self.build_auto_devops if self.auto_devops&.enabled.nil?
- self.auto_devops.update! deploy_strategy: value
+ (auto_devops || build_auto_devops).deploy_strategy = value
end
end
diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb
index af387c99f3d..0648b4a78e1 100644
--- a/app/models/concerns/protected_ref.rb
+++ b/app/models/concerns/protected_ref.rb
@@ -47,7 +47,7 @@ module ProtectedRef
def access_levels_for_ref(ref, action:, protected_refs: nil)
self.matching(ref, protected_refs: protected_refs)
- .map(&:"#{action}_access_levels").flatten
+ .flat_map(&:"#{action}_access_levels")
end
# Returns all protected refs that match the given ref name.
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index e4fe46d722a..6d3c7a7ed68 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -1,5 +1,26 @@
# frozen_string_literal: true
+# This module makes it possible to handle items as a list, where the order of items can be easily altered
+# Requirements:
+#
+# - Only works for ActiveRecord models
+# - relative_position integer field must present on the model
+# - This module uses GROUP BY: the model should have a parent relation, example: project -> issues, project is the parent relation (issues table has a parent_id column)
+#
+# Setup like this in the body of your class:
+#
+# include RelativePositioning
+#
+# # base query used for the position calculation
+# def self.relative_positioning_query_base(issue)
+# where(deleted: false)
+# end
+#
+# # column that should be used in GROUP BY
+# def self.relative_positioning_parent_column
+# :project_id
+# end
+#
module RelativePositioning
extend ActiveSupport::Concern
@@ -8,12 +29,8 @@ module RelativePositioning
MAX_POSITION = Gitlab::Database::MAX_INT_VALUE
IDEAL_DISTANCE = 500
- included do
- after_save :save_positionable_neighbours
- end
-
class_methods do
- def move_to_end(objects)
+ def move_nulls_to_end(objects)
objects = objects.reject(&:relative_position)
return if objects.empty?
@@ -22,7 +39,7 @@ module RelativePositioning
self.transaction do
objects.each do |object|
- relative_position = position_between(max_relative_position, MAX_POSITION)
+ relative_position = position_between(max_relative_position || START_POSITION, MAX_POSITION)
object.relative_position = relative_position
max_relative_position = relative_position
object.save(touch: false)
@@ -93,11 +110,12 @@ module RelativePositioning
return move_after(before) unless after
return move_before(after) unless before
- # If there is no place to insert an issue we need to create one by moving the before issue closer
- # to its predecessor. This process will recursively move all the predecessors until we have a place
+ # If there is no place to insert an item we need to create one by moving the item
+ # before this and all preceding items until there is a gap
+ before, after = after, before if after.relative_position < before.relative_position
if (after.relative_position - before.relative_position) < 2
- before.move_before
- @positionable_neighbours = [before] # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ after.move_sequence_before
+ before.reset
end
self.relative_position = self.class.position_between(before.relative_position, after.relative_position)
@@ -107,12 +125,8 @@ module RelativePositioning
pos_before = before.relative_position
pos_after = before.next_relative_position
- if before.shift_after?
- issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_after)
- issue_to_move.move_after
- @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
-
- pos_after = issue_to_move.relative_position
+ if pos_after && (pos_after - pos_before) < 2
+ before.move_sequence_after
end
self.relative_position = self.class.position_between(pos_before, pos_after)
@@ -122,12 +136,8 @@ module RelativePositioning
pos_after = after.relative_position
pos_before = after.prev_relative_position
- if after.shift_before?
- issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_before)
- issue_to_move.move_before
- @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
-
- pos_before = issue_to_move.relative_position
+ if pos_before && (pos_after - pos_before) < 2
+ after.move_sequence_before
end
self.relative_position = self.class.position_between(pos_before, pos_after)
@@ -141,46 +151,95 @@ module RelativePositioning
self.relative_position = self.class.position_between(min_relative_position || START_POSITION, MIN_POSITION)
end
- # Indicates if there is an issue that should be shifted to free the place
- def shift_after?
- next_pos = next_relative_position
- next_pos && (next_pos - relative_position) == 1
+ # Moves the sequence before the current item to the middle of the next gap
+ # For example, we have 5 11 12 13 14 15 and the current item is 15
+ # This moves the sequence 11 12 13 14 to 8 9 10 11
+ def move_sequence_before
+ next_gap = find_next_gap_before
+ delta = optimum_delta_for_gap(next_gap)
+
+ move_sequence(next_gap[:start], relative_position, -delta)
end
- # Indicates if there is an issue that should be shifted to free the place
- def shift_before?
- prev_pos = prev_relative_position
- prev_pos && (relative_position - prev_pos) == 1
+ # Moves the sequence after the current item to the middle of the next gap
+ # For example, we have 11 12 13 14 15 21 and the current item is 11
+ # This moves the sequence 12 13 14 15 to 15 16 17 18
+ def move_sequence_after
+ next_gap = find_next_gap_after
+ delta = optimum_delta_for_gap(next_gap)
+
+ move_sequence(relative_position, next_gap[:start], delta)
end
private
- # rubocop:disable Gitlab/ModuleWithInstanceVariables
- def save_positionable_neighbours
- return unless @positionable_neighbours
+ # Supposing that we have a sequence of items: 1 5 11 12 13 and the current item is 13
+ # This would return: `{ start: 11, end: 5 }`
+ def find_next_gap_before
+ items_with_next_pos = scoped_items
+ .select('relative_position AS pos, LEAD(relative_position) OVER (ORDER BY relative_position DESC) AS next_pos')
+ .where('relative_position <= ?', relative_position)
+ .order(relative_position: :desc)
+
+ find_next_gap(items_with_next_pos).tap do |gap|
+ gap[:end] ||= MIN_POSITION
+ end
+ end
+
+ # Supposing that we have a sequence of items: 13 14 15 20 24 and the current item is 13
+ # This would return: `{ start: 15, end: 20 }`
+ def find_next_gap_after
+ items_with_next_pos = scoped_items
+ .select('relative_position AS pos, LEAD(relative_position) OVER (ORDER BY relative_position ASC) AS next_pos')
+ .where('relative_position >= ?', relative_position)
+ .order(:relative_position)
- status = @positionable_neighbours.all? { |issue| issue.save(touch: false) }
- @positionable_neighbours = nil
+ find_next_gap(items_with_next_pos).tap do |gap|
+ gap[:end] ||= MAX_POSITION
+ end
+ end
+
+ def find_next_gap(items_with_next_pos)
+ gap = self.class.from(items_with_next_pos, :items_with_next_pos)
+ .where('ABS(pos - next_pos) > 1 OR next_pos IS NULL')
+ .limit(1)
+ .pluck(:pos, :next_pos)
+ .first
+
+ { start: gap[0], end: gap[1] }
+ end
- status
+ def optimum_delta_for_gap(gap)
+ delta = ((gap[:start] - gap[:end]) / 2.0).abs.ceil
+
+ [delta, IDEAL_DISTANCE].min
+ end
+
+ def move_sequence(start_pos, end_pos, delta)
+ scoped_items
+ .where.not(id: self.id)
+ .where('relative_position BETWEEN ? AND ?', start_pos, end_pos)
+ .update_all("relative_position = relative_position + #{delta}")
end
- # rubocop:enable Gitlab/ModuleWithInstanceVariables
def calculate_relative_position(calculation)
# When calculating across projects, this is much more efficient than
# MAX(relative_position) without the GROUP BY, due to index usage:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/54276#note_119340977
- relation = self.class
- .in_parents(parent_ids)
+ relation = scoped_items
.order(Gitlab::Database.nulls_last_order('position', 'DESC'))
+ .group(self.class.relative_positioning_parent_column)
.limit(1)
- .group(self.class.parent_column)
relation = yield relation if block_given?
relation
- .pluck(self.class.parent_column, Arel.sql("#{calculation}(relative_position) AS position"))
+ .pluck(self.class.relative_positioning_parent_column, Arel.sql("#{calculation}(relative_position) AS position"))
.first&.
last
end
+
+ def scoped_items
+ self.class.relative_positioning_query_base(self)
+ end
end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 9becab632f3..116e8967651 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -33,29 +33,12 @@ module Routable
#
# Returns a single object, or nil.
def find_by_full_path(path, follow_redirects: false)
- # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
- # any literal matches come first, for this we have to use "BINARY".
- # Without this there's still no guarantee in what order MySQL will return
- # rows.
- #
- # Why do we do this?
- #
- # Even though we have Rails validation on Route for unique paths
- # (case-insensitive), there are old projects in our DB (and possibly
- # clients' DBs) that have the same path with different cases.
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/18603. Also note that
- # our unique index is case-sensitive in Postgres.
- binary = Gitlab::Database.mysql? ? 'BINARY' : ''
- order_sql = Arel.sql("(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)")
+ order_sql = Arel.sql("(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)")
found = where_full_path_in([path]).reorder(order_sql).take
return found if found
if follow_redirects
- if Gitlab::Database.postgresql?
- joins(:redirect_routes).find_by("LOWER(redirect_routes.path) = LOWER(?)", path)
- else
- joins(:redirect_routes).find_by(redirect_routes: { path: path })
- end
+ joins(:redirect_routes).find_by("LOWER(redirect_routes.path) = LOWER(?)", path)
end
end
@@ -67,27 +50,13 @@ module Routable
#
# Returns an ActiveRecord::Relation.
def where_full_path_in(paths)
- wheres = []
- cast_lower = Gitlab::Database.postgresql?
+ return none if paths.empty?
- paths.each do |path|
- path = connection.quote(path)
-
- where =
- if cast_lower
- "(LOWER(routes.path) = LOWER(#{path}))"
- else
- "(routes.path = #{path})"
- end
-
- wheres << where
+ wheres = paths.map do |path|
+ "(LOWER(routes.path) = LOWER(#{connection.quote(path)}))"
end
- if wheres.empty?
- none
- else
- joins(:route).where(wheres.join(' OR '))
- end
+ joins(:route).where(wheres.join(' OR '))
end
end
diff --git a/app/models/concerns/stepable.rb b/app/models/concerns/stepable.rb
new file mode 100644
index 00000000000..d00a049a004
--- /dev/null
+++ b/app/models/concerns/stepable.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Stepable
+ extend ActiveSupport::Concern
+
+ def steps
+ self.class._all_steps
+ end
+
+ def execute_steps
+ initial_result = {}
+
+ steps.inject(initial_result) do |previous_result, callback|
+ result = method(callback).call
+
+ if result[:status] == :error
+ result[:failed_step] = callback
+
+ break result
+ end
+
+ previous_result.merge(result)
+ end
+ end
+
+ class_methods do
+ def _all_steps
+ @_all_steps ||= []
+ end
+
+ def steps(*methods)
+ _all_steps.concat methods
+ end
+ end
+end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index b42adad94ba..8b536a123fc 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -15,7 +15,8 @@ module Taskable
INCOMPLETE_PATTERN = /(\[[\s]\])/.freeze
ITEM_PATTERN = %r{
^
- \s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list
+ (?:(?:>\s{0,4})*) # optional blockquote characters
+ \s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list
\s+ # whitespace prefix has to be always presented for a list item
(\[\s\]|\[[xX]\]) # checkbox
(\s.+) # followed by whitespace and some text.
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 1293df571a3..4099039dd96 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -3,10 +3,8 @@
module TokenAuthenticatable
extend ActiveSupport::Concern
- private
-
class_methods do
- private # rubocop:disable Lint/UselessAccessModifier
+ private
def add_authentication_token_field(token_field, options = {})
if token_authenticatable_fields.include?(token_field)
diff --git a/app/models/concerns/update_project_statistics.rb b/app/models/concerns/update_project_statistics.rb
index 570a735973f..869b3490f3f 100644
--- a/app/models/concerns/update_project_statistics.rb
+++ b/app/models/concerns/update_project_statistics.rb
@@ -73,15 +73,10 @@ module UpdateProjectStatistics
def schedule_namespace_aggregation_worker
run_after_commit do
- next unless schedule_aggregation_worker?
+ next if project.nil?
Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id)
end
end
-
- def schedule_aggregation_worker?
- !project.nil? &&
- Feature.enabled?(:update_statistics_namespace, project.root_ancestor)
- end
end
end
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 39e12ac2b06..2a5ae7930e6 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -70,10 +70,14 @@ class ContainerRepository < ApplicationRecord
digests = tags.map { |tag| tag.digest }.to_set
digests.all? do |digest|
- client.delete_repository_tag(self.path, digest)
+ delete_tag_by_digest(digest)
end
end
+ def delete_tag_by_digest(digest)
+ client.delete_repository_tag(self.path, digest)
+ end
+
def self.build_from_path(path)
self.new(project: path.repository_project,
name: path.repository_name)
@@ -86,4 +90,9 @@ class ContainerRepository < ApplicationRecord
def self.build_root_repository(project)
self.new(project: project, name: '')
end
+
+ def self.find_by_path!(path)
+ self.find_by!(project: path.repository_project,
+ name: path.repository_name)
+ end
end
diff --git a/app/models/cycle_analytics/group_level.rb b/app/models/cycle_analytics/group_level.rb
new file mode 100644
index 00000000000..a41e1375484
--- /dev/null
+++ b/app/models/cycle_analytics/group_level.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module CycleAnalytics
+ class GroupLevel
+ include LevelBase
+ attr_reader :options, :group
+
+ def initialize(group:, options:)
+ @group = group
+ @options = options.merge(group: group)
+ end
+
+ def summary
+ @summary ||= ::Gitlab::CycleAnalytics::GroupStageSummary.new(group, options: options).data
+ end
+
+ def permissions(*)
+ STAGES.each_with_object({}) do |stage, obj|
+ obj[stage] = true
+ end
+ end
+
+ def stats
+ @stats ||= STAGES.map do |stage_name|
+ self[stage_name].as_json(serializer: GroupAnalyticsStageSerializer)
+ end
+ end
+ end
+end
diff --git a/app/models/cycle_analytics/base.rb b/app/models/cycle_analytics/level_base.rb
index d7b28cd1b67..543349ebf8f 100644
--- a/app/models/cycle_analytics/base.rb
+++ b/app/models/cycle_analytics/level_base.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
module CycleAnalytics
- class Base
+ module LevelBase
STAGES = %i[issue plan code test review staging production].freeze
def all_medians_by_stage
STAGES.each_with_object({}) do |stage_name, medians_per_stage|
- medians_per_stage[stage_name] = self[stage_name].median
+ medians_per_stage[stage_name] = self[stage_name].project_median
end
end
@@ -21,7 +21,7 @@ module CycleAnalytics
end
def [](stage_name)
- Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options)
+ Gitlab::CycleAnalytics::Stage[stage_name].new(options: options)
end
end
end
diff --git a/app/models/cycle_analytics/project_level.rb b/app/models/cycle_analytics/project_level.rb
index 22631cc7d41..4aa426c58a1 100644
--- a/app/models/cycle_analytics/project_level.rb
+++ b/app/models/cycle_analytics/project_level.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
module CycleAnalytics
- class ProjectLevel < Base
+ class ProjectLevel
+ include LevelBase
attr_reader :project, :options
def initialize(project, options:)
@project = project
- @options = options
+ @options = options.merge(project: project)
end
def summary
diff --git a/app/models/dashboard_group_milestone.rb b/app/models/dashboard_group_milestone.rb
index 74aa04ab7d0..ec52f1ed370 100644
--- a/app/models/dashboard_group_milestone.rb
+++ b/app/models/dashboard_group_milestone.rb
@@ -15,8 +15,7 @@ class DashboardGroupMilestone < GlobalMilestone
milestones = Milestone.of_groups(groups.select(:id))
.reorder_by_due_date_asc
.order_by_name_asc
- .active
milestones = milestones.search_title(params[:search_title]) if params[:search_title].present?
- milestones.map { |m| new(m) }
+ Milestone.filter_by_state(milestones, params[:state]).map { |m| new(m) }
end
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index b69cda4f2f9..68586e7a1fd 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -128,17 +128,8 @@ class Deployment < ApplicationRecord
merge_requests = merge_requests.where("merge_request_metrics.merged_at >= ?", previous_deployment.finished_at)
end
- # Need to use `map` instead of `select` because MySQL doesn't allow `SELECT`ing from the same table
- # that we're updating.
- merge_request_ids =
- if Gitlab::Database.postgresql?
- merge_requests.select(:id)
- elsif Gitlab::Database.mysql?
- merge_requests.map(&:id)
- end
-
MergeRequest::Metrics
- .where(merge_request_id: merge_request_ids, first_deployed_to_production_at: nil)
+ .where(merge_request_id: merge_requests.select(:id), first_deployed_to_production_at: nil)
.update_all(first_deployed_to_production_at: finished_at)
end
diff --git a/app/models/deployment_metrics.rb b/app/models/deployment_metrics.rb
index cfe762ca25e..2056c8bc59c 100644
--- a/app/models/deployment_metrics.rb
+++ b/app/models/deployment_metrics.rb
@@ -44,18 +44,7 @@ class DeploymentMetrics
end
end
- # TODO remove fallback case to deployment_platform_cluster.
- # Otherwise we will continue to pay the performance penalty described in
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/63475
- #
- # Removal issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/64105
def cluster_prometheus
- cluster_with_fallback = cluster || deployment_platform_cluster
-
- cluster_with_fallback.application_prometheus if cluster_with_fallback&.application_prometheus_available?
- end
-
- def deployment_platform_cluster
- deployment.environment.deployment_platform&.cluster
+ cluster.application_prometheus if cluster&.application_prometheus_available?
end
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index f75c32633b1..861185dc222 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -20,7 +20,6 @@ class DiffNote < Note
validates :noteable_type, inclusion: { in: -> (_note) { noteable_types } }
validate :positions_complete
validate :verify_supported
- validate :diff_refs_match_commit, if: :for_commit?
before_validation :set_line_code, if: :on_text?
after_save :keep_around_commits
@@ -154,12 +153,6 @@ class DiffNote < Note
errors.add(:position, "is invalid")
end
- def diff_refs_match_commit
- return if self.original_position.diff_refs == self.commit.diff_refs
-
- errors.add(:commit_id, 'does not match the diff refs')
- end
-
def keep_around_commits
shas = [
self.original_position.base_sha,
diff --git a/app/models/environment.rb b/app/models/environment.rb
index b8ee54c1696..513427ac2c5 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -4,11 +4,6 @@ class Environment < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include ReactiveCaching
- # Used to generate random suffixes for the slug
- LETTERS = ('a'..'z').freeze
- NUMBERS = ('0'..'9').freeze
- SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a
-
belongs_to :project, required: true
has_many :deployments, -> { success }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -203,47 +198,13 @@ class Environment < ApplicationRecord
super.presence || generate_slug
end
- # An environment name is not necessarily suitable for use in URLs, DNS
- # or other third-party contexts, so provide a slugified version. A slug has
- # the following properties:
- # * contains only lowercase letters (a-z), numbers (0-9), and '-'
- # * begins with a letter
- # * has a maximum length of 24 bytes (OpenShift limitation)
- # * cannot end with `-`
- def generate_slug
- # Lowercase letters and numbers only
- slugified = +name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
-
- # Must start with a letter
- slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0])
-
- # Repeated dashes are invalid (OpenShift limitation)
- slugified.gsub!(/\-+/, '-')
-
- # Maximum length: 24 characters (OpenShift limitation)
- slugified = slugified[0..23]
-
- # Cannot end with a dash (Kubernetes label limitation)
- slugified.chop! if slugified.end_with?('-')
-
- # Add a random suffix, shortening the current string if necessary, if it
- # has been slugified. This ensures uniqueness.
- if slugified != name
- slugified = slugified[0..16]
- slugified << '-' unless slugified.end_with?('-')
- slugified << random_suffix
- end
-
- self.slug = slugified
- end
-
def external_url_for(path, commit_sha)
return unless self.external_url
public_path = project.public_path_for_source_path(path, commit_sha)
return unless public_path
- [external_url, public_path].join('/')
+ [external_url.delete_suffix('/'), public_path.delete_prefix('/')].join('/')
end
def expire_etag_cache
@@ -274,11 +235,7 @@ class Environment < ApplicationRecord
private
- # Slugifying a name may remove the uniqueness guarantee afforded by it being
- # based on name (which must be unique). To compensate, we add a random
- # 6-byte suffix in those circumstances. This is not *guaranteed* uniqueness,
- # but the chance of collisions is vanishingly small
- def random_suffix
- (0..5).map { SUFFIX_CHARS.sample }.join
+ def generate_slug
+ self.slug = Gitlab::Slug::Environment.new(name).generate
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 9520db1bc0a..74eb556b1b5 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -10,7 +10,6 @@ class Group < Namespace
include Referable
include SelectForProjectAuthorization
include LoadedInGroupList
- include Descendant
include GroupDescendant
include TokenAuthenticatable
include WithUploads
@@ -144,6 +143,12 @@ class Group < Namespace
notification_settings(hierarchy_order: hierarchy_order).where(user: user)
end
+ def notification_email_for(user)
+ # Finds the closest notification_setting with a `notification_email`
+ notification_settings = notification_settings_for(user, hierarchy_order: :asc)
+ notification_settings.find { |n| n.notification_email.present? }&.notification_email
+ end
+
def to_reference(_from = nil, full: nil)
"#{self.class.reference_prefix}#{full_path}"
end
@@ -382,7 +387,7 @@ class Group < Namespace
variables = Ci::GroupVariable.where(group: list_of_ids)
variables = variables.unprotected unless project.protected_for?(ref)
variables = variables.group_by(&:group_id)
- list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
+ list_of_ids.reverse.flat_map { |group| variables[group.id] }.compact
end
def group_member(user)
@@ -416,6 +421,10 @@ class Group < Namespace
super || ::Gitlab::CurrentSettings.default_project_creation
end
+ def subgroup_creation_level
+ super || ::Gitlab::Access::OWNER_SUBGROUP_ACCESS
+ end
+
private
def update_two_factor_requirement
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index 90b4588a325..3d54d17e787 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -14,8 +14,10 @@ class SystemHook < WebHook
default_value_for :repository_update_events, true
default_value_for :merge_requests_events, false
+ validates :url, system_hook_url: true
+
# Allow urls pointing localhost and the local network
def allow_local_requests?
- true
+ Gitlab::CurrentSettings.allow_local_requests_from_system_hooks?
end
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index daf7ff4b771..16fc7fdbd48 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -15,8 +15,8 @@ class WebHook < ApplicationRecord
has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- validates :url, presence: true, public_url: { allow_localhost: lambda(&:allow_local_requests?),
- allow_local_network: lambda(&:allow_local_requests?) }
+ validates :url, presence: true
+ validates :url, public_url: true, unless: ->(hook) { hook.is_a?(SystemHook) }
validates :token, format: { without: /\n/ }
validates :push_events_branch_filter, branch_filter: true
@@ -35,6 +35,6 @@ class WebHook < ApplicationRecord
# Allow urls pointing localhost and the local network
def allow_local_requests?
- false
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 982a94315bd..164858dc432 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -13,11 +13,8 @@ class Issue < ApplicationRecord
include RelativePositioning
include TimeTrackable
include ThrottledTouch
- include IgnorableColumn
include LabelEventable
- ignore_column :assignee_id, :branch_name, :deleted_at
-
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
@@ -94,11 +91,11 @@ class Issue < ApplicationRecord
end
end
- class << self
- alias_method :in_parents, :in_projects
+ def self.relative_positioning_query_base(issue)
+ in_projects(issue.parent_ids)
end
- def self.parent_column
+ def self.relative_positioning_parent_column
:project_id
end
@@ -134,7 +131,7 @@ class Issue < ApplicationRecord
when 'due_date' then order_due_date_asc
when 'due_date_asc' then order_due_date_asc
when 'due_date_desc' then order_due_date_desc
- when 'relative_position' then order_relative_position_asc
+ when 'relative_position' then order_relative_position_asc.with_order_id_desc
else
super
end
diff --git a/app/models/label.rb b/app/models/label.rb
index b83e0862bab..25de26b8384 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -33,7 +33,7 @@ class Label < ApplicationRecord
default_scope { order(title: :asc) }
- scope :templates, -> { where(template: true) }
+ scope :templates, -> { where(template: true, type: [Label.name, nil]) }
scope :with_title, ->(title) { where(title: title) }
scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) }
scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) }
@@ -137,6 +137,10 @@ class Label < ApplicationRecord
where(id: ids)
end
+ def self.on_project_board?(project_id, label_id)
+ on_project_boards(project_id).where(id: label_id).exists?
+ end
+
def open_issues_count(user = nil)
issues_count(user, state: 'opened')
end
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 4cba69069bb..f6b19317c50 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class GroupMember < Member
+ include FromUnion
+
SOURCE_TYPE = 'Namespace'.freeze
belongs_to :group, foreign_key: 'source_id'
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index ba57fefd8f1..5e8a6a7d5e5 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -7,7 +7,6 @@ class MergeRequest < ApplicationRecord
include Noteable
include Referable
include Presentable
- include IgnorableColumn
include TimeTrackable
include ManualInverseAssociation
include EachBatch
@@ -24,10 +23,6 @@ class MergeRequest < ApplicationRecord
SORTING_PREFERENCE_FIELD = :merge_requests_sort
- ignore_column :locked_at,
- :ref_fetched,
- :deleted_at
-
belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project"
belongs_to :merge_user, class_name: "User"
@@ -757,7 +752,7 @@ class MergeRequest < ApplicationRecord
end
def check_mergeability
- MergeRequests::MergeabilityCheckService.new(self).execute
+ MergeRequests::MergeabilityCheckService.new(self).execute(retry_lease: false)
end
# rubocop: enable CodeReuse/ServiceClass
@@ -1254,15 +1249,8 @@ class MergeRequest < ApplicationRecord
end
def all_commits
- # MySQL doesn't support LIMIT in a subquery.
- diffs_relation = if Gitlab::Database.postgresql?
- merge_request_diffs.recent
- else
- merge_request_diffs
- end
-
MergeRequestDiffCommit
- .where(merge_request_diff: diffs_relation)
+ .where(merge_request_diff: merge_request_diffs.recent)
.limit(10_000)
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 37c129e843a..60266992ee1 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -4,8 +4,8 @@ class Milestone < ApplicationRecord
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
MilestoneStruct = Struct.new(:title, :name, :id)
- None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
- Any = MilestoneStruct.new('Any Milestone', '', -1)
+ None = MilestoneStruct.new('No Milestone', 'No Milestone', -1)
+ Any = MilestoneStruct.new('Any Milestone', '', nil)
Upcoming = MilestoneStruct.new('Upcoming', '#upcoming', -2)
Started = MilestoneStruct.new('Started', '#started', -3)
@@ -149,29 +149,10 @@ class Milestone < ApplicationRecord
end
def self.upcoming_ids(projects, groups)
- rel = unscoped
- .for_projects_and_groups(projects, groups)
- .active.where('milestones.due_date > CURRENT_DATE')
-
- if Gitlab::Database.postgresql?
- rel.order(:project_id, :group_id, :due_date).select('DISTINCT ON (project_id, group_id) id')
- else
- # We need to use MySQL's NULL-safe comparison operator `<=>` here
- # because one of `project_id` or `group_id` is always NULL
- join_clause = <<~HEREDOC
- LEFT OUTER JOIN milestones earlier_milestones
- ON milestones.project_id <=> earlier_milestones.project_id
- AND milestones.group_id <=> earlier_milestones.group_id
- AND milestones.due_date > earlier_milestones.due_date
- AND earlier_milestones.due_date > CURRENT_DATE
- AND earlier_milestones.state = 'active'
- HEREDOC
-
- rel
- .joins(join_clause)
- .where('earlier_milestones.id IS NULL')
- .select(:id)
- end
+ unscoped
+ .for_projects_and_groups(projects, groups)
+ .active.where('milestones.due_date > CURRENT_DATE')
+ .order(:project_id, :group_id, :due_date).select('DISTINCT ON (project_id, group_id) id')
end
def participants
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b3021fab7f1..058350b16ce 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -8,13 +8,10 @@ class Namespace < ApplicationRecord
include AfterCommitQueue
include Storage::LegacyNamespace
include Gitlab::SQL::Pattern
- include IgnorableColumn
include FeatureGate
include FromUnion
include Gitlab::Utils::StrongMemoize
- ignore_column :deleted_at
-
# Prevent users from creating unreasonably deep level of nesting.
# The number 20 was taken based on maximum nesting level of
# Android repo (15) + some extra backup.
@@ -335,8 +332,6 @@ class Namespace < ApplicationRecord
end
def force_share_with_group_lock_on_descendants
- return unless Group.supports_nested_objects?
-
# We can't use `descendants.update_all` since Rails will throw away the WITH
# RECURSIVE statement. We also can't use WHERE EXISTS since we can't use
# different table aliases, hence we're just using WHERE IN. Since we have a
diff --git a/app/models/namespace/aggregation_schedule.rb b/app/models/namespace/aggregation_schedule.rb
index 0bef352cf24..61a7eb4b576 100644
--- a/app/models/namespace/aggregation_schedule.rb
+++ b/app/models/namespace/aggregation_schedule.rb
@@ -6,21 +6,13 @@ class Namespace::AggregationSchedule < ApplicationRecord
self.primary_key = :namespace_id
- DEFAULT_LEASE_TIMEOUT = 3.hours
+ DEFAULT_LEASE_TIMEOUT = 1.5.hours.to_i
REDIS_SHARED_KEY = 'gitlab:update_namespace_statistics_delay'.freeze
belongs_to :namespace
after_create :schedule_root_storage_statistics
- def self.delay_timeout
- redis_timeout = Gitlab::Redis::SharedState.with do |redis|
- redis.get(REDIS_SHARED_KEY)
- end
-
- redis_timeout.nil? ? DEFAULT_LEASE_TIMEOUT : redis_timeout.to_i
- end
-
def schedule_root_storage_statistics
run_after_commit_or_now do
try_obtain_lease do
@@ -28,7 +20,7 @@ class Namespace::AggregationSchedule < ApplicationRecord
.perform_async(namespace_id)
Namespaces::RootStatisticsWorker
- .perform_in(self.class.delay_timeout, namespace_id)
+ .perform_in(DEFAULT_LEASE_TIMEOUT, namespace_id)
end
end
end
@@ -37,7 +29,7 @@ class Namespace::AggregationSchedule < ApplicationRecord
# Used by ExclusiveLeaseGuard
def lease_timeout
- self.class.delay_timeout
+ DEFAULT_LEASE_TIMEOUT
end
# Used by ExclusiveLeaseGuard
diff --git a/app/models/note.rb b/app/models/note.rb
index 5c31cff9816..a12d1eb7243 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -27,6 +27,10 @@ class Note < ApplicationRecord
def values
constants.map {|const| self.const_get(const)}
end
+
+ def value?(val)
+ values.include?(val)
+ end
end
end
@@ -292,7 +296,7 @@ class Note < ApplicationRecord
end
def special_role=(role)
- raise "Role is undefined, #{role} not found in #{SpecialRole.values}" unless SpecialRole.values.include?(role)
+ raise "Role is undefined, #{role} not found in #{SpecialRole.values}" unless SpecialRole.value?(role)
@special_role = role
end
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index e6e491634ab..27c122d3559 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -22,7 +22,7 @@ class PagesDomain < ApplicationRecord
validate :validate_pages_domain
validate :validate_matching_key, if: ->(domain) { domain.certificate.present? || domain.key.present? }
- validate :validate_intermediates, if: ->(domain) { domain.certificate.present? }
+ validate :validate_intermediates, if: ->(domain) { domain.certificate.present? && domain.certificate_changed? }
attr_encrypted :key,
mode: :per_attribute_iv_and_salt,
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index f69f0e2dccb..7ae431eaad7 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -7,6 +7,7 @@ class PersonalAccessToken < ApplicationRecord
add_authentication_token_field :token, digest: true
REDIS_EXPIRY_TIME = 3.minutes
+ TOKEN_LENGTH = 20
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
diff --git a/app/models/project.rb b/app/models/project.rb
index f6f7d373f91..8f234fba04f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -31,7 +31,6 @@ class Project < ApplicationRecord
include FeatureGate
include OptionallySearch
include FromUnion
- include IgnorableColumn
extend Gitlab::Cache::RequestCache
extend Gitlab::ConfigHelper
@@ -56,8 +55,6 @@ class Project < ApplicationRecord
VALID_MIRROR_PORTS = [22, 80, 443].freeze
VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
- ignore_column :import_status, :import_jid, :import_error
-
cache_markdown_field :description, pipeline: :description
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
@@ -280,7 +277,7 @@ class Project < ApplicationRecord
has_many :project_deploy_tokens
has_many :deploy_tokens, through: :project_deploy_tokens
- has_one :auto_devops, class_name: 'ProjectAutoDevops'
+ has_one :auto_devops, class_name: 'ProjectAutoDevops', inverse_of: :project, autosave: true
has_many :custom_attributes, class_name: 'ProjectCustomAttribute'
has_many :project_badges, class_name: 'ProjectBadge'
@@ -360,6 +357,7 @@ 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(star_count: :desc) }
scope :sorted_by_stars_asc, -> { reorder(star_count: :asc) }
+ scope :sorted_by_name_asc_limited, ->(limit) { reorder(name: :asc).limit(limit) }
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
@@ -417,12 +415,6 @@ class Project < ApplicationRecord
.where(project_ci_cd_settings: { group_runners_enabled: true })
end
- scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do
- subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.project_id = projects.id')
-
- where('NOT EXISTS (?)', subquery)
- end
-
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
chronic_duration_attr :build_timeout_human_readable, :build_timeout,
@@ -444,22 +436,6 @@ class Project < ApplicationRecord
without_deleted.find_by_id(id)
end
- # Paginates a collection using a `WHERE id < ?` condition.
- #
- # before - A project ID to use for filtering out projects with an equal or
- # greater ID. If no ID is given, all projects are included.
- #
- # limit - The maximum number of rows to include.
- def self.paginate_in_descending_order_using_id(
- before: nil,
- limit: Kaminari.config.default_per_page
- )
- relation = order_id_desc.limit(limit)
- relation = relation.where('projects.id < ?', before) if before
-
- relation
- end
-
def self.eager_load_namespace_and_owner
includes(namespace: :owner)
end
@@ -737,16 +713,27 @@ class Project < ApplicationRecord
repository.commits_by(oids: oids)
end
- # ref can't be HEAD, can only be branch/tag name or SHA
- def latest_successful_build_for(job_name, ref = default_branch)
- latest_pipeline = ci_pipelines.latest_successful_for(ref)
+ # ref can't be HEAD, can only be branch/tag name
+ def latest_successful_build_for_ref(job_name, ref = default_branch)
+ return unless ref
+
+ latest_pipeline = ci_pipelines.latest_successful_for_ref(ref)
+ return unless latest_pipeline
+
+ latest_pipeline.builds.latest.with_artifacts_archive.find_by(name: job_name)
+ end
+
+ def latest_successful_build_for_sha(job_name, sha)
+ return unless sha
+
+ latest_pipeline = ci_pipelines.latest_successful_for_sha(sha)
return unless latest_pipeline
latest_pipeline.builds.latest.with_artifacts_archive.find_by(name: job_name)
end
- def latest_successful_build_for!(job_name, ref = default_branch)
- latest_successful_build_for(job_name, ref) || raise(ActiveRecord::RecordNotFound.new("Couldn't find job #{job_name}"))
+ def latest_successful_build_for_ref!(job_name, ref = default_branch)
+ latest_successful_build_for_ref(job_name, ref) || raise(ActiveRecord::RecordNotFound.new("Couldn't find job #{job_name}"))
end
def merge_base_commit(first_commit_id, second_commit_id)
@@ -1499,12 +1486,20 @@ class Project < ApplicationRecord
!namespace.share_with_group_lock
end
- def pipeline_for(ref, sha = nil)
+ def pipeline_for(ref, sha = nil, id = nil)
+ if id.present?
+ pipelines_for(ref, sha).find_by(id: id)
+ else
+ pipelines_for(ref, sha).take
+ end
+ end
+
+ def pipelines_for(ref, sha = nil)
sha ||= commit(ref).try(:sha)
return unless sha
- ci_pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
+ ci_pipelines.order(id: :desc).where(sha: sha, ref: ref)
end
def latest_successful_pipeline_for_default_branch
@@ -1513,12 +1508,12 @@ class Project < ApplicationRecord
end
@latest_successful_pipeline_for_default_branch =
- ci_pipelines.latest_successful_for(default_branch)
+ ci_pipelines.latest_successful_for_ref(default_branch)
end
def latest_successful_pipeline_for(ref = nil)
if ref && ref != default_branch
- ci_pipelines.latest_successful_for(ref)
+ ci_pipelines.latest_successful_for_ref(ref)
else
latest_successful_pipeline_for_default_branch
end
@@ -1872,16 +1867,24 @@ class Project < ApplicationRecord
end
def append_or_update_attribute(name, value)
- old_values = public_send(name.to_s) # rubocop:disable GitlabSecurity/PublicSend
+ if Project.reflect_on_association(name).try(:macro) == :has_many
+ # if this is 1-to-N relation, update the parent object
+ value.each do |item|
+ item.update!(
+ Project.reflect_on_association(name).foreign_key => id)
+ end
+
+ # force to drop relation cache
+ public_send(name).reset # rubocop:disable GitlabSecurity/PublicSend
- if Project.reflect_on_association(name).try(:macro) == :has_many && old_values.any?
- update_attribute(name, old_values + value)
+ # succeeded
+ true
else
+ # if this is another relation or attribute, update just object
update_attribute(name, value)
end
-
- rescue ActiveRecord::RecordNotSaved => e
- handle_update_attribute_error(e, value)
+ rescue ActiveRecord::RecordInvalid => e
+ raise e, "Failed to set #{name}: #{e.message}"
end
# Tries to set repository as read_only, checking for existing Git transfers in progress beforehand
@@ -2270,18 +2273,6 @@ class Project < ApplicationRecord
ContainerRepository.build_root_repository(self).has_tags?
end
- def handle_update_attribute_error(ex, value)
- if ex.message.start_with?('Failed to replace')
- if value.respond_to?(:each)
- invalid = value.detect(&:invalid?)
-
- raise ex, ([ex.message] + invalid.errors.full_messages).join(' ') if invalid
- end
- end
-
- raise ex
- end
-
def fetch_branch_allows_collaboration(user, branch_name = nil)
return false unless user
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index 67c12363a3c..e11d0c48b4b 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -1,11 +1,7 @@
# frozen_string_literal: true
class ProjectAutoDevops < ApplicationRecord
- include IgnorableColumn
-
- ignore_column :domain
-
- belongs_to :project
+ belongs_to :project, inverse_of: :auto_devops
enum deploy_strategy: {
continuous: 0,
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 7ff06655de0..78e82955342 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -86,6 +86,8 @@ class ProjectFeature < ApplicationRecord
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(:pages_access_level, allows_nil: false) { |feature| feature.project&.public? ? ENABLED : PRIVATE }
+
def feature_available?(feature, user)
# This feature might not be behind a feature flag at all, so default to true
return false unless ::Feature.enabled?(feature, user, default_enabled: true)
diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb
index 62aec4351db..4edf263433f 100644
--- a/app/models/project_services/chat_message/pipeline_message.rb
+++ b/app/models/project_services/chat_message/pipeline_message.rb
@@ -1,24 +1,47 @@
# frozen_string_literal: true
+require 'slack-notifier'
module ChatMessage
class PipelineMessage < BaseMessage
+ MAX_VISIBLE_JOBS = 10
+
+ attr_reader :user
attr_reader :ref_type
attr_reader :ref
attr_reader :status
+ attr_reader :detailed_status
attr_reader :duration
+ attr_reader :finished_at
attr_reader :pipeline_id
+ attr_reader :failed_stages
+ attr_reader :failed_jobs
+
+ attr_reader :project
+ attr_reader :commit
+ attr_reader :committer
+ attr_reader :pipeline
def initialize(data)
super
+ @user = data[:user]
@user_name = data.dig(:user, :username) || 'API'
pipeline_attributes = data[:object_attributes]
@ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
@ref = pipeline_attributes[:ref]
@status = pipeline_attributes[:status]
+ @detailed_status = pipeline_attributes[:detailed_status]
@duration = pipeline_attributes[:duration].to_i
+ @finished_at = pipeline_attributes[:finished_at] ? Time.parse(pipeline_attributes[:finished_at]).to_i : nil
@pipeline_id = pipeline_attributes[:id]
+ @failed_jobs = Array(data[:builds]).select { |b| b[:status] == 'failed' }.reverse # Show failed jobs from oldest to newest
+ @failed_stages = @failed_jobs.map { |j| j[:stage] }.uniq
+
+ @project = Project.find(data[:project][:id])
+ @commit = project.commit_by(oid: data[:commit][:id])
+ @committer = commit.committer
+ @pipeline = Ci::Pipeline.find(pipeline_id)
end
def pretext
@@ -28,38 +51,145 @@ module ChatMessage
def attachments
return message if markdown
- [{ text: format(message), color: attachment_color }]
+ return [{ text: format(message), color: attachment_color }] unless fancy_notifications?
+
+ [{
+ fallback: format(message),
+ color: attachment_color,
+ author_name: user_combined_name,
+ author_icon: user_avatar,
+ author_link: author_url,
+ title: s_("ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}") %
+ {
+ pipeline_id: pipeline_id,
+ humanized_status: humanized_status,
+ duration: pretty_duration(duration)
+ },
+ title_link: pipeline_url,
+ fields: attachments_fields,
+ footer: project.name,
+ footer_icon: project.avatar_url,
+ ts: finished_at
+ }]
end
def activity
{
- title: "Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_combined_name} #{humanized_status}",
- subtitle: "in #{project_link}",
- text: "in #{pretty_duration(duration)}",
+ title: s_("ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status}") %
+ {
+ pipeline_link: pipeline_link,
+ ref_type: ref_type,
+ branch_link: branch_link,
+ user_combined_name: user_combined_name,
+ humanized_status: humanized_status
+ },
+ subtitle: s_("ChatMessage|in %{project_link}") % { project_link: project_link },
+ text: s_("ChatMessage|in %{duration}") % { duration: pretty_duration(duration) },
image: user_avatar || ''
}
end
private
+ def fancy_notifications?
+ Feature.enabled?(:fancy_pipeline_slack_notifications, default_enabled: true)
+ end
+
+ def failed_stages_field
+ {
+ title: s_("ChatMessage|Failed stage").pluralize(failed_stages.length),
+ value: Slack::Notifier::LinkFormatter.format(failed_stages_links),
+ short: true
+ }
+ end
+
+ def failed_jobs_field
+ {
+ title: s_("ChatMessage|Failed job").pluralize(failed_jobs.length),
+ value: Slack::Notifier::LinkFormatter.format(failed_jobs_links),
+ short: true
+ }
+ end
+
+ def yaml_error_field
+ {
+ title: s_("ChatMessage|Invalid CI config YAML file"),
+ value: pipeline.yaml_errors,
+ short: false
+ }
+ end
+
+ def attachments_fields
+ fields = [
+ {
+ title: ref_type == "tag" ? s_("ChatMessage|Tag") : s_("ChatMessage|Branch"),
+ value: Slack::Notifier::LinkFormatter.format(ref_name_link),
+ short: true
+ },
+ {
+ title: s_("ChatMessage|Commit"),
+ value: Slack::Notifier::LinkFormatter.format(commit_link),
+ short: true
+ }
+ ]
+
+ fields << failed_stages_field if failed_stages.any?
+ fields << failed_jobs_field if failed_jobs.any?
+ fields << yaml_error_field if pipeline.has_yaml_errors?
+
+ fields
+ end
+
def message
- "#{project_link}: Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_combined_name} #{humanized_status} in #{pretty_duration(duration)}"
+ s_("ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status} in %{duration}") %
+ {
+ project_link: project_link,
+ pipeline_link: pipeline_link,
+ ref_type: ref_type,
+ branch_link: branch_link,
+ user_combined_name: user_combined_name,
+ humanized_status: humanized_status,
+ duration: pretty_duration(duration)
+ }
end
def humanized_status
- case status
- when 'success'
- 'passed'
+ if fancy_notifications?
+ case status
+ when 'success'
+ detailed_status == "passed with warnings" ? s_("ChatMessage|has passed with warnings") : s_("ChatMessage|has passed")
+ when 'failed'
+ s_("ChatMessage|has failed")
+ else
+ status
+ end
else
- status
+ case status
+ when 'success'
+ s_("ChatMessage|passed")
+ when 'failed'
+ s_("ChatMessage|failed")
+ else
+ status
+ end
end
end
def attachment_color
- if status == 'success'
- 'good'
+ if fancy_notifications?
+ case status
+ when 'success'
+ detailed_status == 'passed with warnings' ? 'warning' : 'good'
+ else
+ 'danger'
+ end
else
- 'danger'
+ case status
+ when 'success'
+ 'good'
+ else
+ 'danger'
+ end
end
end
@@ -71,16 +201,83 @@ module ChatMessage
"[#{ref}](#{branch_url})"
end
+ def project_url
+ project.web_url
+ end
+
def project_link
- "[#{project_name}](#{project_url})"
+ "[#{project.name}](#{project_url})"
+ end
+
+ def pipeline_failed_jobs_url
+ "#{project_url}/pipelines/#{pipeline_id}/failures"
end
def pipeline_url
- "#{project_url}/pipelines/#{pipeline_id}"
+ if fancy_notifications? && failed_jobs.any?
+ pipeline_failed_jobs_url
+ else
+ "#{project_url}/pipelines/#{pipeline_id}"
+ end
end
def pipeline_link
"[##{pipeline_id}](#{pipeline_url})"
end
+
+ def job_url(job)
+ "#{project_url}/-/jobs/#{job[:id]}"
+ end
+
+ def job_link(job)
+ "[#{job[:name]}](#{job_url(job)})"
+ end
+
+ def failed_jobs_links
+ failed = failed_jobs.slice(0, MAX_VISIBLE_JOBS)
+ truncated = failed_jobs.slice(MAX_VISIBLE_JOBS, failed_jobs.size)
+
+ failed_links = failed.map { |job| job_link(job) }
+
+ unless truncated.blank?
+ failed_links << s_("ChatMessage|and [%{count} more](%{pipeline_failed_jobs_url})") % {
+ count: truncated.size,
+ pipeline_failed_jobs_url: pipeline_failed_jobs_url
+ }
+ end
+
+ failed_links.join(I18n.translate(:'support.array.words_connector'))
+ end
+
+ def stage_link(stage)
+ # All stages link to the pipeline page
+ "[#{stage}](#{pipeline_url})"
+ end
+
+ def failed_stages_links
+ failed_stages.map { |s| stage_link(s) }.join(I18n.translate(:'support.array.words_connector'))
+ end
+
+ def commit_url
+ Gitlab::UrlBuilder.build(commit)
+ end
+
+ def commit_link
+ "[#{commit.title}](#{commit_url})"
+ end
+
+ def commits_page_url
+ "#{project_url}/commits/#{ref}"
+ end
+
+ def ref_name_link
+ "[#{ref}](#{commits_page_url})"
+ end
+
+ def author_url
+ return unless user && committer
+
+ Gitlab::UrlBuilder.build(committer)
+ end
end
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index e571700fd02..d08fcd8954d 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -31,7 +31,7 @@ class JiraService < IssueTrackerService
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
def self.reference_pattern(only_long: true)
- @reference_pattern ||= /(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)/
+ @reference_pattern ||= /(?<issue>\b#{Gitlab::Regex.jira_issue_key_regex})/
end
def initialize_properties
@@ -54,7 +54,7 @@ class JiraService < IssueTrackerService
username: self.username,
password: self.password,
site: URI.join(url, '/').to_s, # Intended to find the root
- context_path: url.path.chomp('/'),
+ context_path: url.path,
auth_type: :basic,
read_timeout: 120,
use_cookies: true,
@@ -103,6 +103,12 @@ class JiraService < IssueTrackerService
"#{url}/secure/CreateIssue.jspa"
end
+ alias_method :original_url, :url
+
+ def url
+ original_url&.chomp('/')
+ end
+
def execute(push)
# This method is a no-op, because currently JiraService does not
# support any events.
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 3802d258664..47999a3694e 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -93,13 +93,7 @@ class ProjectStatistics < ApplicationRecord
def schedule_namespace_aggregation_worker
run_after_commit do
- next unless schedule_aggregation_worker?
-
Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id)
end
end
-
- def schedule_aggregation_worker?
- Feature.enabled?(:update_statistics_namespace, project&.root_ancestor)
- end
end
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
index 2e4769364c6..22f60802257 100644
--- a/app/models/redirect_route.rb
+++ b/app/models/redirect_route.rb
@@ -11,11 +11,7 @@ class RedirectRoute < ApplicationRecord
uniqueness: { case_sensitive: false }
scope :matching_path_and_descendants, -> (path) do
- wheres = if Gitlab::Database.postgresql?
- 'LOWER(redirect_routes.path) = LOWER(?) OR LOWER(redirect_routes.path) LIKE LOWER(?)'
- else
- 'redirect_routes.path = ? OR redirect_routes.path LIKE ?'
- end
+ wheres = 'LOWER(redirect_routes.path) = LOWER(?) OR LOWER(redirect_routes.path) LIKE LOWER(?)'
where(wheres, path, "#{sanitize_sql_like(path)}/%")
end
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index af705b29f7a..6b5605f9999 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -31,7 +31,7 @@ class RemoteMirror < ApplicationRecord
scope :enabled, -> { where(enabled: true) }
scope :started, -> { with_update_status(:started) }
- scope :stuck, -> { started.where('last_update_at < ? OR (last_update_at IS NULL AND updated_at < ?)', 1.day.ago, 1.day.ago) }
+ scope :stuck, -> { started.where('last_update_at < ? OR (last_update_at IS NULL AND updated_at < ?)', 1.hour.ago, 3.hours.ago) }
state_machine :update_status, initial: :none do
event :update_start do
@@ -173,7 +173,7 @@ class RemoteMirror < ApplicationRecord
result = URI.parse(url)
result.password = '*****' if result.password
- result.user = '*****' if result.user && result.user != "git" # tokens or other data may be saved as user
+ result.user = '*****' if result.user && result.user != 'git' # tokens or other data may be saved as user
result.to_s
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 187382ad182..a89f573e3d6 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -7,6 +7,9 @@ class Repository
REF_KEEP_AROUND = 'keep-around'.freeze
REF_ENVIRONMENTS = 'environments'.freeze
+ ARCHIVE_CACHE_TIME = 60 # Cache archives referred to by a (mutable) ref for 1 minute
+ ARCHIVE_CACHE_TIME_IMMUTABLE = 3600 # Cache archives referred to by an immutable reference for 1 hour
+
RESERVED_REFS_NAMES = %W[
heads
tags
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 55da37c9545..9a2640db9ca 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -16,7 +16,7 @@ class SystemNoteMetadata < ApplicationRecord
commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved
opened closed merged duplicate locked unlocked
- outdated tag due_date
+ outdated tag due_date pinned_embed
].freeze
validates :note, presence: true
diff --git a/app/models/user.rb b/app/models/user.rb
index 0fd3daa3383..b439d1c0c16 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1259,6 +1259,11 @@ class User < ApplicationRecord
end
end
+ def notification_email_for(notification_group)
+ # Return group-specific email address if present, otherwise return global notification email address
+ notification_group&.notification_email_for(self) || notification_email
+ end
+
def notification_settings_for(source)
if notification_settings.loaded?
notification_settings.find do |notification|
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index f1326f4c8cb..b236250c24e 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -26,7 +26,7 @@ class UserPreference < ApplicationRecord
def set_notes_filter(filter_id, issuable)
# No need to update the column if the value is already set.
- if filter_id && NOTES_FILTERS.values.include?(filter_id)
+ if filter_id && NOTES_FILTERS.value?(filter_id)
field = notes_filter_field_for(issuable)
self[field] = filter_id