Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/ability.rb1
-rw-r--r--app/models/abuse_report.rb15
-rw-r--r--app/models/active_session.rb11
-rw-r--r--app/models/alerting/project_alerting_setting.rb28
-rw-r--r--app/models/analytics/cycle_analytics/runtime_limiter.rb30
-rw-r--r--app/models/analytics/cycle_analytics/stage_event_hash.rb2
-rw-r--r--app/models/application_setting.rb71
-rw-r--r--app/models/application_setting_implementation.rb24
-rw-r--r--app/models/approval.rb3
-rw-r--r--app/models/award_emoji.rb2
-rw-r--r--app/models/badge.rb2
-rw-r--r--app/models/blob_viewer/binary_stl.rb2
-rw-r--r--app/models/blob_viewer/cargo_toml.rb2
-rw-r--r--app/models/blob_viewer/cartfile.rb2
-rw-r--r--app/models/blob_viewer/changelog.rb2
-rw-r--r--app/models/blob_viewer/composer_json.rb2
-rw-r--r--app/models/blob_viewer/contributing.rb2
-rw-r--r--app/models/blob_viewer/csv.rb2
-rw-r--r--app/models/blob_viewer/gemfile.rb2
-rw-r--r--app/models/blob_viewer/gemspec.rb2
-rw-r--r--app/models/blob_viewer/gitlab_ci_yml.rb2
-rw-r--r--app/models/blob_viewer/go_mod.rb4
-rw-r--r--app/models/blob_viewer/godeps_json.rb2
-rw-r--r--app/models/blob_viewer/license.rb2
-rw-r--r--app/models/blob_viewer/markup.rb2
-rw-r--r--app/models/blob_viewer/notebook.rb2
-rw-r--r--app/models/blob_viewer/open_api.rb2
-rw-r--r--app/models/blob_viewer/package_json.rb2
-rw-r--r--app/models/blob_viewer/pdf.rb2
-rw-r--r--app/models/blob_viewer/podfile.rb2
-rw-r--r--app/models/blob_viewer/podspec.rb2
-rw-r--r--app/models/blob_viewer/podspec_json.rb2
-rw-r--r--app/models/blob_viewer/readme.rb2
-rw-r--r--app/models/blob_viewer/requirements_txt.rb2
-rw-r--r--app/models/blob_viewer/route_map.rb2
-rw-r--r--app/models/blob_viewer/sketch.rb2
-rw-r--r--app/models/blob_viewer/svg.rb2
-rw-r--r--app/models/blob_viewer/yarn_lock.rb2
-rw-r--r--app/models/bulk_imports/batch_tracker.rb2
-rw-r--r--app/models/bulk_imports/entity.rb8
-rw-r--r--app/models/bulk_imports/file_transfer/group_config.rb2
-rw-r--r--app/models/bulk_imports/file_transfer/project_config.rb4
-rw-r--r--app/models/chat_name.rb3
-rw-r--r--app/models/ci/build.rb24
-rw-r--r--app/models/ci/build_need.rb5
-rw-r--r--app/models/ci/build_runner_session.rb2
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/ci/runner.rb10
-rw-r--r--app/models/clusters/agent.rb2
-rw-r--r--app/models/clusters/agent_token.rb12
-rw-r--r--app/models/clusters/platforms/kubernetes.rb2
-rw-r--r--app/models/commit.rb20
-rw-r--r--app/models/commit_range.rb6
-rw-r--r--app/models/commit_signatures/gpg_signature.rb1
-rw-r--r--app/models/commit_status.rb17
-rw-r--r--app/models/concerns/avatarable.rb6
-rw-r--r--app/models/concerns/chronic_duration_attribute.rb7
-rw-r--r--app/models/concerns/ci/deployable.rb6
-rw-r--r--app/models/concerns/ci/has_runner_executor.rb4
-rw-r--r--app/models/concerns/ci/maskable.rb4
-rw-r--r--app/models/concerns/ci/partitionable.rb5
-rw-r--r--app/models/concerns/clusters/agents/authorizations/ci_access/config_scopes.rb2
-rw-r--r--app/models/concerns/cross_database_ignored_tables.rb11
-rw-r--r--app/models/concerns/diff_positionable_note.rb2
-rw-r--r--app/models/concerns/each_batch.rb4
-rw-r--r--app/models/concerns/editable.rb2
-rw-r--r--app/models/concerns/enums/prometheus_metric.rb14
-rw-r--r--app/models/concerns/has_unique_internal_users.rb51
-rw-r--r--app/models/concerns/has_user_type.rb17
-rw-r--r--app/models/concerns/integrations/enable_ssl_verification.rb17
-rw-r--r--app/models/concerns/integrations/reset_secret_fields.rb4
-rw-r--r--app/models/concerns/integrations/slack_mattermost_fields.rb43
-rw-r--r--app/models/concerns/issuable.rb17
-rw-r--r--app/models/concerns/issue_available_features.rb8
-rw-r--r--app/models/concerns/linkable_item.rb1
-rw-r--r--app/models/concerns/mentionable/reference_regexes.rb2
-rw-r--r--app/models/concerns/noteable.rb27
-rw-r--r--app/models/concerns/packages/nuget/version_normalizable.rb2
-rw-r--r--app/models/concerns/pg_full_text_searchable.rb6
-rw-r--r--app/models/concerns/protected_ref.rb5
-rw-r--r--app/models/concerns/redactable.rb2
-rw-r--r--app/models/concerns/require_email_verification.rb5
-rw-r--r--app/models/concerns/resolvable_discussion.rb2
-rw-r--r--app/models/concerns/resolvable_note.rb2
-rw-r--r--app/models/concerns/restricted_signup.rb2
-rw-r--r--app/models/concerns/routable.rb71
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb2
-rw-r--r--app/models/concerns/taskable.rb6
-rw-r--r--app/models/concerns/transitionable.rb21
-rw-r--r--app/models/concerns/users/visitable.rb18
-rw-r--r--app/models/concerns/with_uploads.rb2
-rw-r--r--app/models/container_expiration_policy.rb4
-rw-r--r--app/models/container_registry/event.rb12
-rw-r--r--app/models/custom_emoji.rb2
-rw-r--r--app/models/deploy_key.rb2
-rw-r--r--app/models/deploy_token.rb4
-rw-r--r--app/models/description_version.rb2
-rw-r--r--app/models/design_management.rb2
-rw-r--r--app/models/diff_note.rb2
-rw-r--r--app/models/discussion_note.rb2
-rw-r--r--app/models/draft_note.rb4
-rw-r--r--app/models/environment.rb7
-rw-r--r--app/models/environment_status.rb3
-rw-r--r--app/models/error_tracking/project_error_tracking_setting.rb2
-rw-r--r--app/models/event.rb5
-rw-r--r--app/models/gpg_key.rb2
-rw-r--r--app/models/group.rb25
-rw-r--r--app/models/hooks/web_hook.rb2
-rw-r--r--app/models/hooks/web_hook_log.rb11
-rw-r--r--app/models/instance_configuration.rb2
-rw-r--r--app/models/integration.rb6
-rw-r--r--app/models/integrations/apple_app_store.rb4
-rw-r--r--app/models/integrations/asana.rb5
-rw-r--r--app/models/integrations/assembla.rb2
-rw-r--r--app/models/integrations/base_chat_notification.rb89
-rw-r--r--app/models/integrations/base_issue_tracker.rb2
-rw-r--r--app/models/integrations/base_monitoring.rb2
-rw-r--r--app/models/integrations/base_slack_notification.rb14
-rw-r--r--app/models/integrations/base_slash_commands.rb2
-rw-r--r--app/models/integrations/base_third_party_wiki.rb2
-rw-r--r--app/models/integrations/buildkite.rb2
-rw-r--r--app/models/integrations/campfire.rb30
-rw-r--r--app/models/integrations/chat_message/base_message.rb2
-rw-r--r--app/models/integrations/chat_message/deployment_message.rb8
-rw-r--r--app/models/integrations/confluence.rb10
-rw-r--r--app/models/integrations/datadog.rb4
-rw-r--r--app/models/integrations/discord.rb28
-rw-r--r--app/models/integrations/drone_ci.rb2
-rw-r--r--app/models/integrations/emails_on_push.rb2
-rw-r--r--app/models/integrations/external_wiki.rb2
-rw-r--r--app/models/integrations/gitlab_slack_application.rb17
-rw-r--r--app/models/integrations/hangouts_chat.rb6
-rw-r--r--app/models/integrations/jenkins.rb2
-rw-r--r--app/models/integrations/jira.rb2
-rw-r--r--app/models/integrations/mattermost.rb3
-rw-r--r--app/models/integrations/microsoft_teams.rb26
-rw-r--r--app/models/integrations/packagist.rb2
-rw-r--r--app/models/integrations/pivotaltracker.rb2
-rw-r--r--app/models/integrations/prometheus.rb12
-rw-r--r--app/models/integrations/pumble.rb6
-rw-r--r--app/models/integrations/pushover.rb24
-rw-r--r--app/models/integrations/shimo.rb4
-rw-r--r--app/models/integrations/slack.rb4
-rw-r--r--app/models/integrations/teamcity.rb4
-rw-r--r--app/models/integrations/telegram.rb29
-rw-r--r--app/models/integrations/unify_circuit.rb6
-rw-r--r--app/models/integrations/webex_teams.rb6
-rw-r--r--app/models/integrations/zentao.rb6
-rw-r--r--app/models/issuable_severity.rb10
-rw-r--r--app/models/issue.rb54
-rw-r--r--app/models/label_link.rb2
-rw-r--r--app/models/lfs_download_object.rb2
-rw-r--r--app/models/license_template.rb6
-rw-r--r--app/models/loose_foreign_keys/modification_tracker.rb22
-rw-r--r--app/models/loose_foreign_keys/turbo_modification_tracker.rb25
-rw-r--r--app/models/members/group_member.rb13
-rw-r--r--app/models/members/project_member.rb2
-rw-r--r--app/models/merge_request.rb55
-rw-r--r--app/models/metrics/dashboard/annotation.rb34
-rw-r--r--app/models/metrics/users_starred_dashboard.rb18
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/ml/model_version.rb2
-rw-r--r--app/models/namespace.rb29
-rw-r--r--app/models/namespace/detail.rb1
-rw-r--r--app/models/namespace/root_storage_statistics.rb6
-rw-r--r--app/models/namespace/traversal_hierarchy.rb13
-rw-r--r--app/models/namespaces/randomized_suffix_path.rb2
-rw-r--r--app/models/note.rb24
-rw-r--r--app/models/notification_setting.rb2
-rw-r--r--app/models/operations/feature_flag.rb2
-rw-r--r--app/models/organizations/organization.rb3
-rw-r--r--app/models/packages/debian.rb8
-rw-r--r--app/models/packages/debian/file_entry.rb2
-rw-r--r--app/models/packages/dependency_link.rb28
-rw-r--r--app/models/packages/ml_model/package.rb19
-rw-r--r--app/models/packages/nuget/metadatum.rb3
-rw-r--r--app/models/packages/nuget/symbol.rb32
-rw-r--r--app/models/packages/package.rb14
-rw-r--r--app/models/packages/protection.rb9
-rw-r--r--app/models/packages/protection/rule.rb21
-rw-r--r--app/models/pages/lookup_path.rb2
-rw-r--r--app/models/pages/virtual_domain.rb27
-rw-r--r--app/models/pages_deployment.rb13
-rw-r--r--app/models/pages_domain.rb13
-rw-r--r--app/models/performance_monitoring/prometheus_metric.rb33
-rw-r--r--app/models/performance_monitoring/prometheus_panel.rb42
-rw-r--r--app/models/performance_monitoring/prometheus_panel_group.rb36
-rw-r--r--app/models/personal_access_token.rb2
-rw-r--r--app/models/plan.rb2
-rw-r--r--app/models/pool_repository.rb10
-rw-r--r--app/models/project.rb80
-rw-r--r--app/models/project_authorization.rb11
-rw-r--r--app/models/project_authorizations/changes.rb2
-rw-r--r--app/models/project_ci_cd_setting.rb1
-rw-r--r--app/models/project_feature.rb8
-rw-r--r--app/models/project_import_state.rb7
-rw-r--r--app/models/project_metrics_setting.rb16
-rw-r--r--app/models/project_setting.rb27
-rw-r--r--app/models/project_team.rb1
-rw-r--r--app/models/releases/link.rb4
-rw-r--r--app/models/repository.rb30
-rw-r--r--app/models/resource_label_event.rb9
-rw-r--r--app/models/resource_state_event.rb2
-rw-r--r--app/models/resource_timebox_event.rb2
-rw-r--r--app/models/review.rb4
-rw-r--r--app/models/self_managed_prometheus_alert_event.rb18
-rw-r--r--app/models/sent_notification.rb4
-rw-r--r--app/models/snippet_repository.rb2
-rw-r--r--app/models/terraform/state.rb2
-rw-r--r--app/models/user.rb149
-rw-r--r--app/models/user_custom_attribute.rb9
-rw-r--r--app/models/user_interacted_project.rb2
-rw-r--r--app/models/user_preference.rb1
-rw-r--r--app/models/users/callout.rb8
-rw-r--r--app/models/users/credit_card_validation.rb27
-rw-r--r--app/models/users/group_visit.rb17
-rw-r--r--app/models/users/project_callout.rb6
-rw-r--r--app/models/users/project_visit.rb17
-rw-r--r--app/models/work_item.rb30
-rw-r--r--app/models/work_items/parent_link.rb3
-rw-r--r--app/models/work_items/related_work_item_link.rb14
-rw-r--r--app/models/work_items/type.rb2
-rw-r--r--app/models/work_items/widgets/linked_items.rb2
-rw-r--r--app/models/x509_certificate.rb2
-rw-r--r--app/models/x509_issuer.rb7
225 files changed, 1352 insertions, 1062 deletions
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 4da4d113a7f..d8510524c1f 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -46,6 +46,7 @@ class Ability
issues.select { |issue| issue.visible_to_user?(user) }
end
end
+ alias_method :work_items_readable_by_user, :issues_readable_by_user
# Returns an Array of MergeRequests that can be read by the given user.
#
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 75c90d370c3..bf25c539830 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -61,10 +61,11 @@ class AbuseReport < ApplicationRecord
validates :screenshot, file_size: { maximum: MAX_FILE_SIZE }
validate :validate_screenshot_is_image
- scope :by_user_id, ->(id) { where(user_id: id) }
- scope :by_reporter_id, ->(id) { where(reporter_id: id) }
+ scope :by_user_id, ->(user_id) { where(user_id: user_id) }
+ scope :by_reporter_id, ->(reporter_id) { where(reporter_id: reporter_id) }
scope :by_category, ->(category) { where(category: category) }
scope :with_users, -> { includes(:reporter, :user) }
+ scope :with_labels, -> { includes(:labels) }
enum category: {
spam: 1,
@@ -141,8 +142,14 @@ class AbuseReport < ApplicationRecord
end
end
- def other_reports_for_user
- user.abuse_reports.id_not_in(id)
+ def past_closed_reports_for_user
+ user.abuse_reports.closed.id_not_in(id)
+ end
+
+ def similar_open_reports_for_user
+ return AbuseReport.none unless open?
+
+ user.abuse_reports.open.by_category(category).id_not_in(id).includes(:reporter)
end
private
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
index 7d025fb7738..e42f9eeef23 100644
--- a/app/models/active_session.rb
+++ b/app/models/active_session.rb
@@ -102,17 +102,16 @@ class ActiveSession
# set marketing cookie when user has active session
def self.set_active_user_cookie(auth)
- auth.cookies[:about_gitlab_active_user] =
+ expiration_time = 2.weeks.from_now
+
+ auth.cookies[:gitlab_user] =
{
value: true,
- domain: Gitlab.config.gitlab.host
+ domain: Gitlab.config.gitlab.host,
+ expires: expiration_time
}
end
- def self.unset_active_user_cookie(auth)
- auth.cookies.delete :about_gitlab_active_user
- end
-
def self.list(user)
Gitlab::Redis::Sessions.with do |redis|
cleaned_up_lookup_entries(redis, user).map do |raw_session|
diff --git a/app/models/alerting/project_alerting_setting.rb b/app/models/alerting/project_alerting_setting.rb
index 34fa27eb29b..7e94d41137f 100644
--- a/app/models/alerting/project_alerting_setting.rb
+++ b/app/models/alerting/project_alerting_setting.rb
@@ -14,6 +14,8 @@ module Alerting
algorithm: 'aes-256-gcm'
before_validation :ensure_token
+ after_create :create_http_integration
+ after_update :sync_http_integration
private
@@ -24,5 +26,31 @@ module Alerting
def generate_token
SecureRandom.hex
end
+
+ # Remove in next required stop after %16.4
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/338838
+ def sync_http_integration
+ project.alert_management_http_integrations
+ .for_endpoint_identifier('legacy-prometheus')
+ .take
+ &.update_columns(
+ encrypted_token: encrypted_token,
+ encrypted_token_iv: encrypted_token_iv
+ )
+ end
+
+ # Remove in next required stop after %16.4
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/338838
+ def create_http_integration
+ AlertManagement::HttpIntegration.insert({
+ project_id: project_id,
+ encrypted_token: encrypted_token,
+ encrypted_token_iv: encrypted_token_iv,
+ active: true,
+ name: 'Prometheus',
+ endpoint_identifier: 'legacy-prometheus',
+ type_identifier: :prometheus
+ })
+ end
end
end
diff --git a/app/models/analytics/cycle_analytics/runtime_limiter.rb b/app/models/analytics/cycle_analytics/runtime_limiter.rb
new file mode 100644
index 00000000000..063377c3ddb
--- /dev/null
+++ b/app/models/analytics/cycle_analytics/runtime_limiter.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Analytics
+ module CycleAnalytics
+ class RuntimeLimiter
+ delegate :monotonic_time, to: :'Gitlab::Metrics::System'
+
+ DEFAULT_MAX_RUNTIME = 200.seconds
+
+ attr_reader :max_runtime, :start_time
+
+ def initialize(max_runtime = DEFAULT_MAX_RUNTIME)
+ @start_time = monotonic_time
+ @max_runtime = max_runtime
+ end
+
+ def elapsed_time
+ monotonic_time - start_time
+ end
+
+ def over_time?
+ @last_check = elapsed_time >= max_runtime
+ end
+
+ def was_over_time?
+ !!@last_check
+ end
+ end
+ end
+end
diff --git a/app/models/analytics/cycle_analytics/stage_event_hash.rb b/app/models/analytics/cycle_analytics/stage_event_hash.rb
index 6443a970945..7dcabd01ebf 100644
--- a/app/models/analytics/cycle_analytics/stage_event_hash.rb
+++ b/app/models/analytics/cycle_analytics/stage_event_hash.rb
@@ -13,7 +13,7 @@ module Analytics
# Atomic, safe insert without retrying
query = <<~SQL
- WITH insert_cte AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (
+ WITH insert_cte AS MATERIALIZED (
INSERT INTO #{quoted_table_name} (hash_sha256) VALUES (#{casted_hash_code}) ON CONFLICT DO NOTHING RETURNING ID
)
SELECT ids.id FROM (
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index f67efaf4f58..153257636ba 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -14,38 +14,30 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
ignore_column :send_user_confirmation_email, remove_with: '15.8', remove_after: '2022-12-18'
ignore_column :web_ide_clientside_preview_enabled, remove_with: '15.11', remove_after: '2023-04-22'
ignore_columns %i[instance_administration_project_id instance_administrators_group_id], remove_with: '16.2', remove_after: '2023-06-22'
- ignore_columns %i[
- encrypted_tofa_access_token_expires_in
- encrypted_tofa_access_token_expires_in_iv
- encrypted_tofa_client_library_args
- encrypted_tofa_client_library_args_iv
- encrypted_tofa_client_library_class
- encrypted_tofa_client_library_class_iv
- encrypted_tofa_client_library_create_credentials_method
- encrypted_tofa_client_library_create_credentials_method_iv
- encrypted_tofa_client_library_fetch_access_token_method
- encrypted_tofa_client_library_fetch_access_token_method_iv
- encrypted_tofa_credentials
- encrypted_tofa_credentials_iv
- encrypted_tofa_host
- encrypted_tofa_host_iv
- encrypted_tofa_request_json_keys
- encrypted_tofa_request_json_keys_iv
- encrypted_tofa_request_payload
- encrypted_tofa_request_payload_iv
- encrypted_tofa_response_json_keys
- encrypted_tofa_response_json_keys_iv
- encrypted_tofa_url
- encrypted_tofa_url_iv
- vertex_project
- ], remove_with: '16.3', remove_after: '2023-07-22'
ignore_column :database_apdex_settings, remove_with: '16.4', remove_after: '2023-08-22'
+
ignore_columns %i[
dashboard_notification_limit
dashboard_enforcement_limit
dashboard_limit_new_namespace_creation_enforcement_date
], remove_with: '16.5', remove_after: '2023-08-22'
+ ignore_column %i[
+ relay_state_domain_allowlist
+ in_product_marketing_emails_enabled
+ ], remove_with: '16.6', remove_after: '2023-10-22'
+
+ ignore_columns %i[
+ encrypted_product_analytics_clickhouse_connection_string
+ encrypted_product_analytics_clickhouse_connection_string_iv
+ encrypted_jitsu_administrator_password
+ encrypted_jitsu_administrator_password_iv
+ jitsu_host
+ jitsu_project_xid
+ jitsu_administrator_email
+ ], remove_with: '16.5', remove_after: '2023-09-22'
+ ignore_columns %i[ai_access_token ai_access_token_iv], remove_with: '16.6', remove_after: '2023-10-22'
+
INSTANCE_REVIEW_MIN_USERS = 50
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
'Admin Area > Settings > Metrics and profiling > Metrics - Grafana'
@@ -244,6 +236,11 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
hostname: true,
if: :snowplow_enabled
+ validates :snowplow_database_collector_hostname,
+ allow_blank: true,
+ hostname: true,
+ length: { maximum: 255 }
+
validates :max_attachment_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
@@ -300,6 +297,10 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
presence: true,
numericality: { only_integer: true, greater_than: 0 }
+ validates :decompress_archive_file_timeout,
+ presence: true,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+
validates :repository_storages, presence: true
validate :check_repository_storages
validate :check_repository_storages_weighted
@@ -310,7 +311,7 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
if: :auto_devops_enabled?
validates :enabled_git_access_protocol,
- inclusion: { in: %w(ssh http), allow_blank: true }
+ inclusion: { in: %w[ssh http], allow_blank: true }
validates :domain_denylist,
presence: { message: 'Domain denylist cannot be empty if denylist is enabled.' },
@@ -551,7 +552,7 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
if: :external_authorization_service_enabled
validates :spam_check_endpoint_url,
- addressable_url: ADDRESSABLE_URL_VALIDATION_OPTIONS.merge({ schemes: %w(tls grpc) }), allow_blank: true
+ addressable_url: ADDRESSABLE_URL_VALIDATION_OPTIONS.merge({ schemes: %w[tls grpc] }), allow_blank: true
validates :spam_check_endpoint_url,
presence: true,
@@ -666,6 +667,10 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
validates :gitlab_shell_operation_limit
end
+ validates :search_rate_limit_allowlist,
+ length: { maximum: 100, message: N_('is too long (maximum is 100 entries)') },
+ allow_nil: false
+
validates :notes_create_limit_allowlist,
length: { maximum: 100, message: N_('is too long (maximum is 100 entries)') },
allow_nil: false
@@ -794,18 +799,20 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
attr_encrypted :arkose_labs_public_api_key, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
attr_encrypted :arkose_labs_private_api_key, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
attr_encrypted :cube_api_key, encryption_options_base_32_aes_256_gcm
- attr_encrypted :jitsu_administrator_password, encryption_options_base_32_aes_256_gcm
attr_encrypted :telesign_customer_xid, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
attr_encrypted :telesign_api_key, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
- attr_encrypted :product_analytics_clickhouse_connection_string, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
attr_encrypted :product_analytics_configurator_connection_string, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
attr_encrypted :openai_api_key, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
attr_encrypted :anthropic_api_key, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
- attr_encrypted :ai_access_token, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
attr_encrypted :vertex_ai_credentials, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
+ # Restricting the validation to `on: :update` only to avoid cyclical dependencies with
+ # License <--> ApplicationSetting. This method calls a license check when we create
+ # ApplicationSetting from defaults which in turn depends on ApplicationSetting record.
+ # The currect default is defined in the `defaults` method so we don't need to validate
+ # it here.
validates :disable_feed_token,
- inclusion: { in: [true, false], message: N_('must be a boolean value') }
+ inclusion: { in: [true, false], message: N_('must be a boolean value') }, on: :update
validates :disable_admin_oauth_scopes,
inclusion: { in: [true, false], message: N_('must be a boolean value') }
@@ -962,7 +969,7 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
end
def parsed_kroki_url
- @parsed_kroki_url ||= Gitlab::UrlBlocker.validate!(kroki_url, schemes: %w(http https), enforce_sanitization: true)[0]
+ @parsed_kroki_url ||= Gitlab::UrlBlocker.validate!(kroki_url, schemes: %w[http https], enforce_sanitization: true)[0]
rescue Gitlab::UrlBlocker::BlockedUrlError => e
self.errors.add(
:kroki_url,
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index f6bf535158a..5a90e246499 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -9,12 +9,12 @@ module ApplicationSettingImplementation
\s # any whitespace character
| # or
[\r\n] # any number of newline characters
- }x.freeze
+ }x
# Setting a key restriction to `-1` means that all keys of this type are
# forbidden.
FORBIDDEN_KEY_VALUE = KeyRestrictionValidator::FORBIDDEN
- VALID_RUNNER_REGISTRAR_TYPES = %w(project group).freeze
+ VALID_RUNNER_REGISTRAR_TYPES = %w[project group].freeze
DEFAULT_PROTECTED_PATHS = [
'/users/password',
@@ -37,7 +37,6 @@ module ApplicationSettingImplementation
{
admin_mode: false,
after_sign_up_text: nil,
- ai_access_token: nil,
akismet_enabled: false,
akismet_api_key: nil,
allow_local_requests_from_system_hooks: true,
@@ -53,6 +52,7 @@ module ApplicationSettingImplementation
container_registry_vendor: '',
container_registry_version: '',
custom_http_clone_url_root: nil,
+ decompress_archive_file_timeout: 210,
default_artifacts_expire_in: '30 days',
default_branch_name: nil,
default_branch_protection: Settings.gitlab['default_branch_protection'],
@@ -171,6 +171,7 @@ module ApplicationSettingImplementation
snowplow_app_id: nil,
snowplow_collector_hostname: nil,
snowplow_cookie_domain: nil,
+ snowplow_database_collector_hostname: nil,
snowplow_enabled: false,
sourcegraph_enabled: false,
sourcegraph_public_only: true,
@@ -254,6 +255,7 @@ module ApplicationSettingImplementation
user_deactivation_emails_enabled: true,
search_rate_limit: 30,
search_rate_limit_unauthenticated: 10,
+ search_rate_limit_allowlist: [],
users_get_by_id_limit: 300,
users_get_by_id_limit_allowlist: [],
can_create_group: true,
@@ -380,6 +382,14 @@ module ApplicationSettingImplementation
self.protected_paths = strings_to_array(values)
end
+ def protected_paths_for_get_request_raw
+ array_to_string(protected_paths_for_get_request)
+ end
+
+ def protected_paths_for_get_request_raw=(values)
+ self.protected_paths_for_get_request = strings_to_array(values)
+ end
+
def notes_create_limit_allowlist_raw
array_to_string(notes_create_limit_allowlist)
end
@@ -396,6 +406,14 @@ module ApplicationSettingImplementation
self.users_get_by_id_limit_allowlist = strings_to_array(values).map(&:downcase)
end
+ def search_rate_limit_allowlist_raw
+ array_to_string(search_rate_limit_allowlist)
+ end
+
+ def search_rate_limit_allowlist_raw=(values)
+ self.search_rate_limit_allowlist = strings_to_array(values).map(&:downcase)
+ end
+
def asset_proxy_whitelist=(values)
values = strings_to_array(values) if values.is_a?(String)
diff --git a/app/models/approval.rb b/app/models/approval.rb
index 9ded44fe425..ecc15077c8d 100644
--- a/app/models/approval.rb
+++ b/app/models/approval.rb
@@ -3,10 +3,13 @@
class Approval < ApplicationRecord
include CreatedAtFilterable
include Importable
+ include ShaAttribute
belongs_to :user
belongs_to :merge_request
+ sha_attribute :patch_id_sha
+
validates :merge_request_id, presence: true, unless: :importing?
validates :user_id, presence: true, uniqueness: { scope: [:merge_request_id] }
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index ebc43b04b1b..73e3fa709b0 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -78,7 +78,7 @@ class AwardEmoji < ApplicationRecord
end
def broadcast_note_update
- awardable.expire_etag_cache
+ awardable.broadcast_noteable_notes_changed
awardable.trigger_note_subscription_update
end
diff --git a/app/models/badge.rb b/app/models/badge.rb
index 23e6f305c32..f4e719887ba 100644
--- a/app/models/badge.rb
+++ b/app/models/badge.rb
@@ -18,7 +18,7 @@ class Badge < ApplicationRecord
# This regex is built dynamically using the keys from the PLACEHOLDER struct.
# So, we can easily add new placeholder just by modifying the PLACEHOLDER hash.
# This regex will build the new PLACEHOLDER_REGEX with the new information
- PLACEHOLDERS_REGEX = /(#{PLACEHOLDERS.keys.join('|')})/.freeze
+ PLACEHOLDERS_REGEX = /(#{PLACEHOLDERS.keys.join('|')})/
default_scope { order_created_at_asc } # rubocop:disable Cop/DefaultScope
diff --git a/app/models/blob_viewer/binary_stl.rb b/app/models/blob_viewer/binary_stl.rb
index 425f72decae..6ccf75200e5 100644
--- a/app/models/blob_viewer/binary_stl.rb
+++ b/app/models/blob_viewer/binary_stl.rb
@@ -6,7 +6,7 @@ module BlobViewer
include ClientSide
self.partial_name = 'stl'
- self.extensions = %w(stl)
+ self.extensions = %w[stl]
self.binary = true
end
end
diff --git a/app/models/blob_viewer/cargo_toml.rb b/app/models/blob_viewer/cargo_toml.rb
index 2f1ebd25b4f..eb2a6f4433d 100644
--- a/app/models/blob_viewer/cargo_toml.rb
+++ b/app/models/blob_viewer/cargo_toml.rb
@@ -4,7 +4,7 @@ module BlobViewer
class CargoToml < DependencyManager
include Static
- self.file_types = %i(cargo_toml)
+ self.file_types = %i[cargo_toml]
def manager_name
'Cargo'
diff --git a/app/models/blob_viewer/cartfile.rb b/app/models/blob_viewer/cartfile.rb
index ea0494033bf..58fc97a9ffc 100644
--- a/app/models/blob_viewer/cartfile.rb
+++ b/app/models/blob_viewer/cartfile.rb
@@ -4,7 +4,7 @@ module BlobViewer
class Cartfile < DependencyManager
include Static
- self.file_types = %i(cartfile)
+ self.file_types = %i[cartfile]
def manager_name
'Carthage'
diff --git a/app/models/blob_viewer/changelog.rb b/app/models/blob_viewer/changelog.rb
index 8810bd25809..7992fbf542c 100644
--- a/app/models/blob_viewer/changelog.rb
+++ b/app/models/blob_viewer/changelog.rb
@@ -6,7 +6,7 @@ module BlobViewer
include Static
self.partial_name = 'changelog'
- self.file_types = %i(changelog)
+ self.file_types = %i[changelog]
self.binary = false
def render_error
diff --git a/app/models/blob_viewer/composer_json.rb b/app/models/blob_viewer/composer_json.rb
index aac7271242e..3449780f50f 100644
--- a/app/models/blob_viewer/composer_json.rb
+++ b/app/models/blob_viewer/composer_json.rb
@@ -4,7 +4,7 @@ module BlobViewer
class ComposerJson < DependencyManager
include ServerSide
- self.file_types = %i(composer_json)
+ self.file_types = %i[composer_json]
def manager_name
'Composer'
diff --git a/app/models/blob_viewer/contributing.rb b/app/models/blob_viewer/contributing.rb
index fa224309e31..524104f176a 100644
--- a/app/models/blob_viewer/contributing.rb
+++ b/app/models/blob_viewer/contributing.rb
@@ -6,7 +6,7 @@ module BlobViewer
include Static
self.partial_name = 'contributing'
- self.file_types = %i(contributing)
+ self.file_types = %i[contributing]
self.binary = false
end
end
diff --git a/app/models/blob_viewer/csv.rb b/app/models/blob_viewer/csv.rb
index 633e3bd63d8..97fa890653d 100644
--- a/app/models/blob_viewer/csv.rb
+++ b/app/models/blob_viewer/csv.rb
@@ -6,7 +6,7 @@ module BlobViewer
include ClientSide
self.binary = false
- self.extensions = %w(csv)
+ self.extensions = %w[csv]
self.partial_name = 'csv'
self.switcher_icon = 'table'
end
diff --git a/app/models/blob_viewer/gemfile.rb b/app/models/blob_viewer/gemfile.rb
index 77220cdbd08..84edacb32bd 100644
--- a/app/models/blob_viewer/gemfile.rb
+++ b/app/models/blob_viewer/gemfile.rb
@@ -4,7 +4,7 @@ module BlobViewer
class Gemfile < DependencyManager
include Static
- self.file_types = %i(gemfile gemfile_lock)
+ self.file_types = %i[gemfile gemfile_lock]
def manager_name
'Bundler'
diff --git a/app/models/blob_viewer/gemspec.rb b/app/models/blob_viewer/gemspec.rb
index 274859a7710..645458467f4 100644
--- a/app/models/blob_viewer/gemspec.rb
+++ b/app/models/blob_viewer/gemspec.rb
@@ -4,7 +4,7 @@ module BlobViewer
class Gemspec < DependencyManager
include ServerSide
- self.file_types = %i(gemspec)
+ self.file_types = %i[gemspec]
def manager_name
'RubyGems'
diff --git a/app/models/blob_viewer/gitlab_ci_yml.rb b/app/models/blob_viewer/gitlab_ci_yml.rb
index e255b6d15d2..9cee536d15b 100644
--- a/app/models/blob_viewer/gitlab_ci_yml.rb
+++ b/app/models/blob_viewer/gitlab_ci_yml.rb
@@ -7,7 +7,7 @@ module BlobViewer
self.partial_name = 'gitlab_ci_yml'
self.loading_partial_name = 'gitlab_ci_yml_loading'
- self.file_types = %i(gitlab_ci)
+ self.file_types = %i[gitlab_ci]
self.binary = false
def validation_message(opts)
diff --git a/app/models/blob_viewer/go_mod.rb b/app/models/blob_viewer/go_mod.rb
index d4d117f899c..eebf057c6dc 100644
--- a/app/models/blob_viewer/go_mod.rb
+++ b/app/models/blob_viewer/go_mod.rb
@@ -11,9 +11,9 @@ module BlobViewer
(?<name>.*?) (?# module name)
\s*(?://.*)? (?# comment)
(?:\n|\z) (?# newline or end of file)
- }x.freeze
+ }x
- self.file_types = %i(go_mod go_sum)
+ self.file_types = %i[go_mod go_sum]
def manager_name
'Go Modules'
diff --git a/app/models/blob_viewer/godeps_json.rb b/app/models/blob_viewer/godeps_json.rb
index 743c759aea5..37a133848a0 100644
--- a/app/models/blob_viewer/godeps_json.rb
+++ b/app/models/blob_viewer/godeps_json.rb
@@ -4,7 +4,7 @@ module BlobViewer
class GodepsJson < DependencyManager
include Static
- self.file_types = %i(godeps_json)
+ self.file_types = %i[godeps_json]
def manager_name
'godep'
diff --git a/app/models/blob_viewer/license.rb b/app/models/blob_viewer/license.rb
index 3427227ad26..489b29380d0 100644
--- a/app/models/blob_viewer/license.rb
+++ b/app/models/blob_viewer/license.rb
@@ -6,7 +6,7 @@ module BlobViewer
include Static
self.partial_name = 'license'
- self.file_types = %i(license)
+ self.file_types = %i[license]
self.binary = false
def license
diff --git a/app/models/blob_viewer/markup.rb b/app/models/blob_viewer/markup.rb
index 6f002a6b224..4b04d8425fd 100644
--- a/app/models/blob_viewer/markup.rb
+++ b/app/models/blob_viewer/markup.rb
@@ -7,7 +7,7 @@ module BlobViewer
self.partial_name = 'markup'
self.extensions = Gitlab::MarkupHelper::EXTENSIONS
- self.file_types = %i(readme)
+ self.file_types = %i[readme]
self.binary = false
def banzai_render_context
diff --git a/app/models/blob_viewer/notebook.rb b/app/models/blob_viewer/notebook.rb
index 351502d451f..e6f1988d7a6 100644
--- a/app/models/blob_viewer/notebook.rb
+++ b/app/models/blob_viewer/notebook.rb
@@ -6,7 +6,7 @@ module BlobViewer
include ClientSide
self.partial_name = 'notebook'
- self.extensions = %w(ipynb)
+ self.extensions = %w[ipynb]
self.binary = false
self.switcher_icon = 'doc-text'
self.switcher_title = 'notebook'
diff --git a/app/models/blob_viewer/open_api.rb b/app/models/blob_viewer/open_api.rb
index 0551f3bb1e3..5d9c5bea8dc 100644
--- a/app/models/blob_viewer/open_api.rb
+++ b/app/models/blob_viewer/open_api.rb
@@ -6,7 +6,7 @@ module BlobViewer
include ClientSide
self.partial_name = 'openapi'
- self.file_types = %i(openapi)
+ self.file_types = %i[openapi]
self.binary = false
self.switcher_icon = 'api'
end
diff --git a/app/models/blob_viewer/package_json.rb b/app/models/blob_viewer/package_json.rb
index 5350b6b0626..c205c10b536 100644
--- a/app/models/blob_viewer/package_json.rb
+++ b/app/models/blob_viewer/package_json.rb
@@ -4,7 +4,7 @@ module BlobViewer
class PackageJson < DependencyManager
include ServerSide
- self.file_types = %i(package_json)
+ self.file_types = %i[package_json]
def manager_name
yarn? ? 'yarn' : 'npm'
diff --git a/app/models/blob_viewer/pdf.rb b/app/models/blob_viewer/pdf.rb
index e3542b91d5c..61957ef4228 100644
--- a/app/models/blob_viewer/pdf.rb
+++ b/app/models/blob_viewer/pdf.rb
@@ -6,7 +6,7 @@ module BlobViewer
include ClientSide
self.partial_name = 'pdf'
- self.extensions = %w(pdf)
+ self.extensions = %w[pdf]
self.binary = true
self.switcher_icon = 'document'
self.switcher_title = 'PDF'
diff --git a/app/models/blob_viewer/podfile.rb b/app/models/blob_viewer/podfile.rb
index 73d714f48ca..dcabcfc4d57 100644
--- a/app/models/blob_viewer/podfile.rb
+++ b/app/models/blob_viewer/podfile.rb
@@ -4,7 +4,7 @@ module BlobViewer
class Podfile < DependencyManager
include Static
- self.file_types = %i(podfile)
+ self.file_types = %i[podfile]
def manager_name
'CocoaPods'
diff --git a/app/models/blob_viewer/podspec.rb b/app/models/blob_viewer/podspec.rb
index 2303471583d..50ca3f5bd16 100644
--- a/app/models/blob_viewer/podspec.rb
+++ b/app/models/blob_viewer/podspec.rb
@@ -4,7 +4,7 @@ module BlobViewer
class Podspec < DependencyManager
include ServerSide
- self.file_types = %i(podspec)
+ self.file_types = %i[podspec]
def manager_name
'CocoaPods'
diff --git a/app/models/blob_viewer/podspec_json.rb b/app/models/blob_viewer/podspec_json.rb
index d606f72376d..03e680e2a8b 100644
--- a/app/models/blob_viewer/podspec_json.rb
+++ b/app/models/blob_viewer/podspec_json.rb
@@ -2,7 +2,7 @@
module BlobViewer
class PodspecJson < Podspec
- self.file_types = %i(podspec_json)
+ self.file_types = %i[podspec_json]
def package_name
@package_name ||= fetch_from_json('name')
diff --git a/app/models/blob_viewer/readme.rb b/app/models/blob_viewer/readme.rb
index f1a5c6a6acc..ec84977d8c5 100644
--- a/app/models/blob_viewer/readme.rb
+++ b/app/models/blob_viewer/readme.rb
@@ -6,7 +6,7 @@ module BlobViewer
include Static
self.partial_name = 'readme'
- self.file_types = %i(readme)
+ self.file_types = %i[readme]
self.binary = false
def visible_to?(current_user)
diff --git a/app/models/blob_viewer/requirements_txt.rb b/app/models/blob_viewer/requirements_txt.rb
index 58161e83493..7322e416c4c 100644
--- a/app/models/blob_viewer/requirements_txt.rb
+++ b/app/models/blob_viewer/requirements_txt.rb
@@ -4,7 +4,7 @@ module BlobViewer
class RequirementsTxt < DependencyManager
include Static
- self.file_types = %i(requirements_txt)
+ self.file_types = %i[requirements_txt]
def manager_name
'pip'
diff --git a/app/models/blob_viewer/route_map.rb b/app/models/blob_viewer/route_map.rb
index 6731536dfe1..a8c64bd5e6a 100644
--- a/app/models/blob_viewer/route_map.rb
+++ b/app/models/blob_viewer/route_map.rb
@@ -7,7 +7,7 @@ module BlobViewer
self.partial_name = 'route_map'
self.loading_partial_name = 'route_map_loading'
- self.file_types = %i(route_map)
+ self.file_types = %i[route_map]
self.binary = false
def validation_message
diff --git a/app/models/blob_viewer/sketch.rb b/app/models/blob_viewer/sketch.rb
index 90bc9be29f4..b7b1d412eff 100644
--- a/app/models/blob_viewer/sketch.rb
+++ b/app/models/blob_viewer/sketch.rb
@@ -6,7 +6,7 @@ module BlobViewer
include ClientSide
self.partial_name = 'sketch'
- self.extensions = %w(sketch)
+ self.extensions = %w[sketch]
self.binary = true
self.switcher_icon = 'doc-image'
self.switcher_title = 'preview'
diff --git a/app/models/blob_viewer/svg.rb b/app/models/blob_viewer/svg.rb
index 60a11fbd97e..afcd3a7c735 100644
--- a/app/models/blob_viewer/svg.rb
+++ b/app/models/blob_viewer/svg.rb
@@ -6,7 +6,7 @@ module BlobViewer
include ServerSide
self.partial_name = 'svg'
- self.extensions = %w(svg)
+ self.extensions = %w[svg]
self.binary = false
self.switcher_icon = 'doc-image'
self.switcher_title = 'image'
diff --git a/app/models/blob_viewer/yarn_lock.rb b/app/models/blob_viewer/yarn_lock.rb
index 196d9f96f23..75369370602 100644
--- a/app/models/blob_viewer/yarn_lock.rb
+++ b/app/models/blob_viewer/yarn_lock.rb
@@ -4,7 +4,7 @@ module BlobViewer
class YarnLock < DependencyManager
include Static
- self.file_types = %i(yarn_lock)
+ self.file_types = %i[yarn_lock]
def manager_name
'Yarn'
diff --git a/app/models/bulk_imports/batch_tracker.rb b/app/models/bulk_imports/batch_tracker.rb
index 2e79d41d46e..eb7fe9f9913 100644
--- a/app/models/bulk_imports/batch_tracker.rb
+++ b/app/models/bulk_imports/batch_tracker.rb
@@ -18,6 +18,8 @@ module BulkImports
event :start do
transition created: :started
+ # To avoid errors when re-starting a pipeline in case of network errors
+ transition started: :started
end
event :retry do
diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb
index 644673e249e..437118c36e8 100644
--- a/app/models/bulk_imports/entity.rb
+++ b/app/models/bulk_imports/entity.rb
@@ -196,6 +196,10 @@ class BulkImports::Entity < ApplicationRecord
update!(has_failures: true)
end
+ def source_version
+ @source_version ||= bulk_import.source_version_info
+ end
+
private
def validate_parent_is_a_group
@@ -240,7 +244,9 @@ class BulkImports::Entity < ApplicationRecord
errors.add(
:source_full_path,
- Gitlab::Regex.bulk_import_source_full_path_regex_message
+ s_('BulkImport|must have a relative path structure with no HTTP ' \
+ 'protocol characters, or leading or trailing forward slashes. Path segments must not start or ' \
+ 'end with a special character, and must not contain consecutive special characters')
)
end
end
diff --git a/app/models/bulk_imports/file_transfer/group_config.rb b/app/models/bulk_imports/file_transfer/group_config.rb
index 6766c00246b..67d53056444 100644
--- a/app/models/bulk_imports/file_transfer/group_config.rb
+++ b/app/models/bulk_imports/file_transfer/group_config.rb
@@ -3,7 +3,7 @@
module BulkImports
module FileTransfer
class GroupConfig < BaseConfig
- SKIPPED_RELATIONS = %w(members).freeze
+ SKIPPED_RELATIONS = %w[members].freeze
def import_export_yaml
::Gitlab::ImportExport.group_config_file
diff --git a/app/models/bulk_imports/file_transfer/project_config.rb b/app/models/bulk_imports/file_transfer/project_config.rb
index 8d4c68f7b5a..890a0fb6ee4 100644
--- a/app/models/bulk_imports/file_transfer/project_config.rb
+++ b/app/models/bulk_imports/file_transfer/project_config.rb
@@ -3,10 +3,10 @@
module BulkImports
module FileTransfer
class ProjectConfig < BaseConfig
- SKIPPED_RELATIONS = %w(
+ SKIPPED_RELATIONS = %w[
project_members
group_members
- ).freeze
+ ].freeze
LFS_OBJECTS_RELATION = 'lfs_objects'
REPOSITORY_BUNDLE_RELATION = 'repository'
diff --git a/app/models/chat_name.rb b/app/models/chat_name.rb
index cda19273f52..d3fbfe3aa55 100644
--- a/app/models/chat_name.rb
+++ b/app/models/chat_name.rb
@@ -3,9 +3,6 @@
class ChatName < ApplicationRecord
LAST_USED_AT_INTERVAL = 1.hour
- include IgnorableColumns
- ignore_column :integration_id, remove_with: '16.0', remove_after: '2023-04-22'
-
belongs_to :user
validates :user, presence: true
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 7a623b0cefb..2abb8e4be48 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -165,7 +165,10 @@ module Ci
scope :with_live_trace, -> { where('EXISTS (?)', Ci::BuildTraceChunk.where("#{quoted_table_name}.id = #{Ci::BuildTraceChunk.quoted_table_name}.build_id").select(1)) }
scope :with_stale_live_trace, -> { with_live_trace.finished_before(12.hours.ago) }
scope :finished_before, -> (date) { finished.where('finished_at < ?', date) }
- scope :license_management_jobs, -> { where(name: %i(license_management license_scanning)) } # handle license rename https://gitlab.com/gitlab-org/gitlab/issues/8911
+ scope :license_management_jobs, -> { where(name: %i[license_management license_scanning]) } # handle license rename https://gitlab.com/gitlab-org/gitlab/issues/8911
+ # WARNING: This scope could lead to performance implications for large size of tables `ci_builds` and ci_runners`.
+ # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123131
+ scope :with_runner_type, -> (runner_type) { joins(:runner).where(runner: { runner_type: runner_type }) }
scope :with_secure_reports_from_config_options, -> (job_types) do
joins(:metadata).where("#{Ci::BuildMetadata.quoted_table_name}.config_options -> 'artifacts' -> 'reports' ?| array[:job_types]", job_types: job_types)
@@ -388,6 +391,9 @@ module Ci
name == 'pages'
end
+ # overridden on EE
+ def pages_path_prefix; end
+
def runnable?
true
end
@@ -408,7 +414,7 @@ module Ci
end
def options_scheduled_at
- ChronicDuration.parse(options[:start_in])&.seconds&.from_now
+ ChronicDuration.parse(options[:start_in], use_complete_matcher: true)&.seconds&.from_now
end
def action?
@@ -487,10 +493,7 @@ module Ci
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless persisted? && persisted_environment.present?
- variables.concat(persisted_environment.predefined_variables)
-
- variables.append(key: 'CI_ENVIRONMENT_ACTION', value: environment_action)
- variables.append(key: 'CI_ENVIRONMENT_TIER', value: environment_tier)
+ variables.append(key: 'CI_ENVIRONMENT_SLUG', value: environment_slug)
# Here we're passing unexpanded environment_url for runner to expand,
# and we need to make sure that CI_ENVIRONMENT_NAME and
@@ -735,7 +738,7 @@ module Ci
def artifacts_expire_in=(value)
self.artifacts_expire_at =
if value
- ChronicDuration.parse(value)&.seconds&.from_now
+ ChronicDuration.parse(value, use_complete_matcher: true)&.seconds&.from_now
end
end
@@ -1039,6 +1042,13 @@ module Ci
end
end
+ def time_in_queue_seconds
+ return if queued_at.nil?
+
+ (::Time.current - queued_at).seconds.to_i
+ end
+ strong_memoize_attr :time_in_queue_seconds
+
protected
def run_status_commit_hooks!
diff --git a/app/models/ci/build_need.rb b/app/models/ci/build_need.rb
index 317f2523f69..00241908644 100644
--- a/app/models/ci/build_need.rb
+++ b/app/models/ci/build_need.rb
@@ -7,15 +7,16 @@ module Ci
include SafelyChangeColumnDefault
include BulkInsertSafe
+ MAX_JOB_NAME_LENGTH = 128
+
columns_changing_default :partition_id
- ignore_column :id_convert_to_bigint, remove_with: '16.4', remove_after: '2023-09-22'
belongs_to :build, class_name: "Ci::Processable", foreign_key: :build_id, inverse_of: :needs
partitionable scope: :build
validates :build, presence: true
- validates :name, presence: true, length: { maximum: 128 }
+ validates :name, presence: true, length: { maximum: MAX_JOB_NAME_LENGTH }
validates :optional, inclusion: { in: [true, false] }
scope :scoped_build, -> { where("#{Ci::Build.quoted_table_name}.id = #{quoted_table_name}.build_id") }
diff --git a/app/models/ci/build_runner_session.rb b/app/models/ci/build_runner_session.rb
index eaa2e1c428e..e197217bb70 100644
--- a/app/models/ci/build_runner_session.rb
+++ b/app/models/ci/build_runner_session.rb
@@ -20,7 +20,7 @@ module Ci
partitionable scope: :build
validates :build, presence: true
- validates :url, public_url: { schemes: %w(https) }
+ validates :url, public_url: { schemes: %w[https] }
def terminal_specification
wss_url = Gitlab::UrlHelpers.as_wss(Addressable::URI.escape(url))
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 3a5db04a687..5bf4e846304 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -162,7 +162,7 @@ module Ci
validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing?
- validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
+ validates :source, exclusion: { in: %w[unknown], unless: :importing? }, on: :create
after_create :keep_around_commits, unless: :importing?
after_find :observe_age_in_minutes, unless: :importing?
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 8d93429fd24..91c919dc662 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -52,7 +52,7 @@ module Ci
RUNNER_QUEUE_EXPIRY_TIME = 1.hour
# The `UPDATE_CONTACT_COLUMN_EVERY` defines how often the Runner DB entry can be updated
- UPDATE_CONTACT_COLUMN_EVERY = (40.minutes..55.minutes).freeze
+ UPDATE_CONTACT_COLUMN_EVERY = (40.minutes..55.minutes)
# The `STALE_TIMEOUT` constant defines the how far past the last contact or creation date a runner will be considered stale
STALE_TIMEOUT = 3.months
@@ -532,7 +532,9 @@ module Ci
'virtualbox' => :virtualbox,
'docker+machine' => :docker_machine,
'docker-ssh+machine' => :docker_ssh_machine,
- 'kubernetes' => :kubernetes
+ 'kubernetes' => :kubernetes,
+ 'docker-autoscaler' => :docker_autoscaler,
+ 'instance' => :instance
}.freeze
EXECUTOR_TYPE_TO_NAMES = EXECUTOR_NAME_TO_TYPES.invert.freeze
@@ -552,9 +554,7 @@ module Ci
end
def cleanup_runner_queue
- Gitlab::Redis::SharedState.with do |redis|
- redis.del(runner_queue_key)
- end
+ ::Gitlab::Workhorse.cleanup_key(runner_queue_key)
end
def runner_queue_key
diff --git a/app/models/clusters/agent.rb b/app/models/clusters/agent.rb
index 8dc866929f3..cbea7efc70e 100644
--- a/app/models/clusters/agent.rb
+++ b/app/models/clusters/agent.rb
@@ -50,7 +50,7 @@ module Clusters
end
def connected?
- agent_tokens.active.where("last_used_at > ?", INACTIVE_AFTER.ago).exists?
+ agent_tokens.connected.exists?
end
def activity_event_deletion_cutoff
diff --git a/app/models/clusters/agent_token.rb b/app/models/clusters/agent_token.rb
index b2b13f6cef7..f4c497a42cc 100644
--- a/app/models/clusters/agent_token.rb
+++ b/app/models/clusters/agent_token.rb
@@ -2,10 +2,15 @@
module Clusters
class AgentToken < ApplicationRecord
+ TOKEN_PREFIX = "glagent-"
+
include RedisCacheable
include TokenAuthenticatable
- add_authentication_token_field :token, encrypted: :required, token_generator: -> { Devise.friendly_token(50) }
+ add_authentication_token_field :token,
+ encrypted: :required,
+ token_generator: -> { Devise.friendly_token(50) },
+ format_with_prefix: :glagent_prefix
cached_attr_reader :last_used_at
self.table_name = 'cluster_agent_tokens'
@@ -21,6 +26,7 @@ module Clusters
scope :order_last_used_at_desc, -> { order(arel_table[:last_used_at].desc.nulls_last) }
scope :with_status, -> (status) { where(status: status) }
scope :active, -> { where(status: :active) }
+ scope :connected, -> { active.where("last_used_at > ?", Clusters::Agent::INACTIVE_AFTER.ago) }
enum status: {
active: 0,
@@ -30,5 +36,9 @@ module Clusters
def to_ability_name
:cluster
end
+
+ def glagent_prefix
+ TOKEN_PREFIX
+ end
end
end
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 123ad0ebfaf..5efbec45561 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -8,7 +8,7 @@ module Clusters
include ReactiveCaching
include NullifyIfBlank
- RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze
+ RESERVED_NAMESPACES = %w[gitlab-managed-apps].freeze
REQUIRED_K8S_MIN_VERSION = 23
IGNORED_CONNECTION_EXCEPTIONS = [
diff --git a/app/models/commit.rb b/app/models/commit.rb
index d7aa66588d3..39e12b53f21 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -30,10 +30,10 @@ class Commit
MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH
MAX_SHA_LENGTH = Gitlab::Git::Commit::MAX_SHA_LENGTH
- COMMIT_SHA_PATTERN = Gitlab::Git::Commit::SHA_PATTERN.freeze
- EXACT_COMMIT_SHA_PATTERN = /\A#{COMMIT_SHA_PATTERN}\z/.freeze
+ COMMIT_SHA_PATTERN = Gitlab::Git::Commit::SHA_PATTERN
+ EXACT_COMMIT_SHA_PATTERN = /\A#{COMMIT_SHA_PATTERN}\z/
# Used by GFM to match and present link extensions on node texts and hrefs.
- LINK_EXTENSION_PATTERN = /(patch)/.freeze
+ LINK_EXTENSION_PATTERN = /(patch)/
DEFAULT_MAX_DIFF_LINES_SETTING = 50_000
DEFAULT_MAX_DIFF_FILES_SETTING = 1_000
@@ -432,7 +432,7 @@ class Commit
end
def cherry_pick_message(user)
- %{#{message}\n\n#{cherry_pick_description(user)}}
+ %(#{message}\n\n#{cherry_pick_description(user)})
end
def revert_description(user)
@@ -444,7 +444,7 @@ class Commit
end
def revert_message(user)
- %{Revert "#{title.strip}"\n\n#{revert_description(user)}}
+ %(Revert "#{title.strip}"\n\n#{revert_description(user)})
end
def reverts_commit?(commit, user)
@@ -539,7 +539,7 @@ class Commit
# added by `git commit --fixup` which is used by some community members.
# https://gitlab.com/gitlab-org/gitlab/-/issues/342937#note_892065311
#
- DRAFT_REGEX = /\A\s*#{Gitlab::Regex.merge_request_draft}|(fixup!|squash!)\s/.freeze
+ DRAFT_REGEX = /\A\s*#{Gitlab::Regex.merge_request_draft}|(fixup!|squash!)\s/
def draft?
!!(title =~ DRAFT_REGEX)
@@ -554,10 +554,10 @@ class Commit
"commit:#{sha}"
end
- def expire_note_etag_cache
+ def broadcast_notes_changed
super
- expire_note_etag_cache_for_related_mrs
+ broadcast_notes_changed_for_related_mrs
end
def readable_by?(user)
@@ -614,8 +614,8 @@ class Commit
end
end
- def expire_note_etag_cache_for_related_mrs
- MergeRequest.includes(target_project: :namespace).by_commit_sha(id).find_each(&:expire_note_etag_cache)
+ def broadcast_notes_changed_for_related_mrs
+ MergeRequest.includes(target_project: :namespace).by_commit_sha(id).find_each(&:broadcast_notes_changed)
end
def commit_reference(from, referable_commit_id, full: false)
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index d882a185464..cb24297f2c8 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -28,11 +28,11 @@ class CommitRange
# The beginning and ending refs can be named or SHAs, and
# the range notation can be double- or triple-dot.
- REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/.freeze
- PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/.freeze
+ REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
+ PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
# In text references, the beginning and ending refs can only be valid SHAs.
- STRICT_PATTERN = /#{Gitlab::Git::Commit::RAW_SHA_PATTERN}\.{2,3}#{Gitlab::Git::Commit::RAW_SHA_PATTERN}/.freeze
+ STRICT_PATTERN = /#{Gitlab::Git::Commit::RAW_SHA_PATTERN}\.{2,3}#{Gitlab::Git::Commit::RAW_SHA_PATTERN}/
def self.reference_prefix
'@'
diff --git a/app/models/commit_signatures/gpg_signature.rb b/app/models/commit_signatures/gpg_signature.rb
index a9e8ca2dd33..45937b68691 100644
--- a/app/models/commit_signatures/gpg_signature.rb
+++ b/app/models/commit_signatures/gpg_signature.rb
@@ -3,6 +3,7 @@ module CommitSignatures
class GpgSignature < ApplicationRecord
include CommitSignature
include SignatureType
+ include EachBatch
sha_attribute :gpg_key_primary_keyid
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index c2425e9460a..3761aa81bf7 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -9,16 +9,19 @@ class CommitStatus < Ci::ApplicationRecord
include BulkInsertableAssociations
include TaggableQueries
- ROUTING_FEATURE_FLAG = :ci_partitioning_use_ci_builds_routing_table
+ def self.switch_table_names
+ if Gitlab::Utils.to_boolean(ENV['USE_CI_BUILDS_ROUTING_TABLE'])
+ :p_ci_builds
+ else
+ :ci_builds
+ end
+ end
- self.table_name = 'ci_builds'
- self.sequence_name = 'ci_builds_id_seq'
+ self.table_name = self.switch_table_names
+ self.sequence_name = :ci_builds_id_seq
self.primary_key = :id
- partitionable scope: :pipeline, through: {
- table: :p_ci_builds,
- flag: ROUTING_FEATURE_FLAG
- }
+ partitionable scope: :pipeline
belongs_to :user
belongs_to :project
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index f419fa8518e..e342939b3d6 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -48,12 +48,6 @@ module Avatarable
end
end
- class_methods do
- def bot_avatar(image:)
- Rails.root.join('lib', 'assets', 'images', 'bot_avatars', image).open
- end
- end
-
def avatar_type
unless self.avatar.image?
errors.add :avatar, "file format is not supported. Please try one of the following supported formats: #{AvatarUploader::SAFE_IMAGE_EXT.join(', ')}"
diff --git a/app/models/concerns/chronic_duration_attribute.rb b/app/models/concerns/chronic_duration_attribute.rb
index af4905115b1..7b7b61fdf06 100644
--- a/app/models/concerns/chronic_duration_attribute.rb
+++ b/app/models/concerns/chronic_duration_attribute.rb
@@ -17,7 +17,12 @@ module ChronicDurationAttribute
chronic_duration_attributes[virtual_attribute] = value.presence || parameters[:default].presence.to_s
begin
- new_value = value.present? ? ChronicDuration.parse(value).to_i : parameters[:default].presence
+ new_value = if value.present?
+ ChronicDuration.parse(value, use_complete_matcher: true).to_i
+ else
+ parameters[:default].presence
+ end
+
assign_attributes(source_attribute => new_value)
rescue ChronicDuration::DurationParseError
# ignore error as it will be caught by validation
diff --git a/app/models/concerns/ci/deployable.rb b/app/models/concerns/ci/deployable.rb
index b3b80989410..d25151f9a34 100644
--- a/app/models/concerns/ci/deployable.rb
+++ b/app/models/concerns/ci/deployable.rb
@@ -138,7 +138,11 @@ module Ci
end
def environment_url
- options&.dig(:environment, :url) || persisted_environment&.external_url
+ options&.dig(:environment, :url) || persisted_environment.try(:external_url)
+ end
+
+ def environment_slug
+ persisted_environment.try(:slug)
end
def environment_status
diff --git a/app/models/concerns/ci/has_runner_executor.rb b/app/models/concerns/ci/has_runner_executor.rb
index dc70cdb2018..6d4622945fe 100644
--- a/app/models/concerns/ci/has_runner_executor.rb
+++ b/app/models/concerns/ci/has_runner_executor.rb
@@ -17,7 +17,9 @@ module Ci
virtualbox: 8,
docker_machine: 9,
docker_ssh_machine: 10,
- kubernetes: 11
+ kubernetes: 11,
+ docker_autoscaler: 12,
+ instance: 13
}, _suffix: true
end
end
diff --git a/app/models/concerns/ci/maskable.rb b/app/models/concerns/ci/maskable.rb
index e2cef0981d1..15240385dd8 100644
--- a/app/models/concerns/ci/maskable.rb
+++ b/app/models/concerns/ci/maskable.rb
@@ -11,12 +11,12 @@ module Ci
# * Minimal length of 8 characters
# * Characters must be from the Base64 alphabet (RFC4648) with the addition of '@', ':', '.', and '~'
# * Absolutely no fun is allowed
- REGEX = %r{\A[a-zA-Z0-9_+=/@:.~-]{8,}\z}.freeze
+ REGEX = %r{\A[a-zA-Z0-9_+=/@:.~-]{8,}\z}
# * Single line
# * No spaces
# * Minimal length of 8 characters
# * Some fun is allowed
- MASK_AND_RAW_REGEX = %r{\A\S{8,}\z}.freeze
+ MASK_AND_RAW_REGEX = %r{\A\S{8,}\z}
included do
validates :masked, inclusion: { in: [true, false] }
diff --git a/app/models/concerns/ci/partitionable.rb b/app/models/concerns/ci/partitionable.rb
index ec6c85d888d..c4b1281fa72 100644
--- a/app/models/concerns/ci/partitionable.rb
+++ b/app/models/concerns/ci/partitionable.rb
@@ -107,7 +107,10 @@ module Ci
partitioned_by :partition_id,
strategy: :ci_sliding_list,
next_partition_if: proc { false },
- detach_partition_if: proc { false }
+ detach_partition_if: proc { false },
+ # Most of the db tasks are run in a weekly basis, e.g. execute_batched_migrations.
+ # Therefore, let's start with 1.week and see how it'd go.
+ analyze_interval: 1.week
end
end
end
diff --git a/app/models/concerns/clusters/agents/authorizations/ci_access/config_scopes.rb b/app/models/concerns/clusters/agents/authorizations/ci_access/config_scopes.rb
index eef68bfd349..9528a708ee1 100644
--- a/app/models/concerns/clusters/agents/authorizations/ci_access/config_scopes.rb
+++ b/app/models/concerns/clusters/agents/authorizations/ci_access/config_scopes.rb
@@ -17,7 +17,7 @@ module Clusters
class_methods do
def available_ci_access_fields(_project)
- %w(agent)
+ %w[agent]
end
end
end
diff --git a/app/models/concerns/cross_database_ignored_tables.rb b/app/models/concerns/cross_database_ignored_tables.rb
index c97e405cce4..14a9703a734 100644
--- a/app/models/concerns/cross_database_ignored_tables.rb
+++ b/app/models/concerns/cross_database_ignored_tables.rb
@@ -4,6 +4,12 @@ module CrossDatabaseIgnoredTables
extend ActiveSupport::Concern
class_methods do
+ def temporary_ignore_cross_database_tables(tables, url:, &blk)
+ Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.temporary_ignore_tables_in_transaction(
+ tables, url: url, &blk
+ )
+ end
+
def cross_database_ignore_tables(tables, options = {})
raise "missing issue url" if options[:url].blank?
@@ -40,8 +46,7 @@ module CrossDatabaseIgnoredTables
return yield unless options[:if].nil? || instance_eval(&options[:if])
url = options[:url]
- Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.temporary_ignore_tables_in_transaction(
- tables, url: url, &blk
- )
+
+ self.class.temporary_ignore_cross_database_tables(tables, url: url, &blk)
end
end
diff --git a/app/models/concerns/diff_positionable_note.rb b/app/models/concerns/diff_positionable_note.rb
index b10b318fb7c..2f64129b65f 100644
--- a/app/models/concerns/diff_positionable_note.rb
+++ b/app/models/concerns/diff_positionable_note.rb
@@ -14,7 +14,7 @@ module DiffPositionableNote
validates :position, json_schema: { filename: "position", hash_conversion: true }
end
- %i(original_position position change_position).each do |meth|
+ %i[original_position position change_position].each do |meth|
define_method "#{meth}=" do |new_position|
if new_position.is_a?(String)
new_position = begin
diff --git a/app/models/concerns/each_batch.rb b/app/models/concerns/each_batch.rb
index 945d286a2fd..0c8cf861c38 100644
--- a/app/models/concerns/each_batch.rb
+++ b/app/models/concerns/each_batch.rb
@@ -54,7 +54,7 @@ module EachBatch
'the column: argument must be set to a column name to use for ordering rows'
end
- start = except(:select)
+ start = except(:select, :includes, :preload)
.select(column)
.reorder(column => order)
@@ -69,7 +69,7 @@ module EachBatch
1.step do |index|
start_cond = arel_table[column].gteq(start_id)
start_cond = arel_table[column].lteq(start_id) if order == :desc
- stop = except(:select)
+ stop = except(:select, :includes, :preload)
.select(column)
.where(start_cond)
.reorder(column => order)
diff --git a/app/models/concerns/editable.rb b/app/models/concerns/editable.rb
index 2e49e720ac9..be9858bf49b 100644
--- a/app/models/concerns/editable.rb
+++ b/app/models/concerns/editable.rb
@@ -8,6 +8,6 @@ module Editable
end
def last_edited_by
- super || User.ghost
+ super || Users::Internal.ghost
end
end
diff --git a/app/models/concerns/enums/prometheus_metric.rb b/app/models/concerns/enums/prometheus_metric.rb
index e65a01990a3..2cc765b7a3c 100644
--- a/app/models/concerns/enums/prometheus_metric.rb
+++ b/app/models/concerns/enums/prometheus_metric.rb
@@ -30,37 +30,37 @@ module Enums
# built-in groups
nginx_ingress_vts: {
group_title: _('Response metrics (NGINX Ingress VTS)'),
- required_metrics: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg),
+ required_metrics: %w[nginx_upstream_responses_total nginx_upstream_response_msecs_avg],
priority: 10
}.freeze,
nginx_ingress: {
group_title: _('Response metrics (NGINX Ingress)'),
- required_metrics: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum),
+ required_metrics: %w[nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum],
priority: 10
}.freeze,
ha_proxy: {
group_title: _('Response metrics (HA Proxy)'),
- required_metrics: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total),
+ required_metrics: %w[haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total],
priority: 10
}.freeze,
aws_elb: {
group_title: _('Response metrics (AWS ELB)'),
- required_metrics: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum),
+ required_metrics: %w[aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum],
priority: 10
}.freeze,
nginx: {
group_title: _('Response metrics (NGINX)'),
- required_metrics: %w(nginx_server_requests nginx_server_requestMsec),
+ required_metrics: %w[nginx_server_requests nginx_server_requestMsec],
priority: 10
}.freeze,
kubernetes: {
group_title: _('System metrics (Kubernetes)'),
- required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total),
+ required_metrics: %w[container_memory_usage_bytes container_cpu_usage_seconds_total],
priority: 5
}.freeze,
cluster_health: {
group_title: _('Cluster Health'),
- required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total),
+ required_metrics: %w[container_memory_usage_bytes container_cpu_usage_seconds_total],
priority: 10
}.freeze
}.merge(custom_group_details).freeze
diff --git a/app/models/concerns/has_unique_internal_users.rb b/app/models/concerns/has_unique_internal_users.rb
deleted file mode 100644
index 25b56f6d70f..00000000000
--- a/app/models/concerns/has_unique_internal_users.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-module HasUniqueInternalUsers
- extend ActiveSupport::Concern
-
- class_methods do
- private
-
- def unique_internal(scope, username, email_pattern, &block)
- scope.first || create_unique_internal(scope, username, email_pattern, &block)
- end
-
- def create_unique_internal(scope, username, email_pattern, &creation_block)
- # Since we only want a single one of these in an instance, we use an
- # exclusive lease to ensure than this block is never run concurrently.
- lease_key = "user:unique_internal:#{username}"
- lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i)
-
- until uuid = lease.try_obtain
- # Keep trying until we obtain the lease. To prevent hammering Redis too
- # much we'll wait for a bit between retries.
- sleep(1)
- end
-
- # Recheck if the user is already present. One might have been
- # added between the time we last checked (first line of this method)
- # and the time we acquired the lock.
- existing_user = uncached { scope.first }
- return existing_user if existing_user.present?
-
- uniquify = Gitlab::Utils::Uniquify.new
-
- username = uniquify.string(username) { |s| User.find_by_username(s) }
-
- email = uniquify.string(-> (n) { Kernel.sprintf(email_pattern, n) }) do |s|
- User.find_by_email(s)
- end
-
- user = scope.build(
- username: username,
- email: email,
- &creation_block
- )
-
- Users::UpdateService.new(user, user: user).execute(validate: false)
- user
- ensure
- Gitlab::ExclusiveLease.cancel(lease_key, uuid)
- end
- end
-end
diff --git a/app/models/concerns/has_user_type.rb b/app/models/concerns/has_user_type.rb
index 2d0ff82e624..c3f702a4e69 100644
--- a/app/models/concerns/has_user_type.rb
+++ b/app/models/concerns/has_user_type.rb
@@ -74,4 +74,21 @@ module HasUserType
# https://gitlab.com/gitlab-org/gitlab/-/issues/346058
'****'
end
+
+ def resource_bot_resource
+ return unless project_bot?
+
+ projects&.first || groups&.first
+ end
+
+ def resource_bot_owners
+ return [] unless project_bot?
+
+ resource = resource_bot_resource
+ return [] unless resource
+
+ return resource.maintainers if resource.is_a?(Project)
+
+ resource.owners
+ end
end
diff --git a/app/models/concerns/integrations/enable_ssl_verification.rb b/app/models/concerns/integrations/enable_ssl_verification.rb
index 11dc8a76a2b..9735a9bf5f6 100644
--- a/app/models/concerns/integrations/enable_ssl_verification.rb
+++ b/app/models/concerns/integrations/enable_ssl_verification.rb
@@ -19,13 +19,16 @@ module Integrations
url_index = fields.index { |field| field[:name].ends_with?('_url') }
insert_index = url_index ? url_index + 1 : -1
- fields.insert(insert_index, {
- type: 'checkbox',
- name: 'enable_ssl_verification',
- title: s_('Integrations|SSL verification'),
- checkbox_label: s_('Integrations|Enable SSL verification'),
- help: s_('Integrations|Clear if using a self-signed certificate.')
- })
+ fields.insert(insert_index,
+ Field.new(
+ name: 'enable_ssl_verification',
+ integration_class: self,
+ type: :checkbox,
+ title: s_('Integrations|SSL verification'),
+ checkbox_label: s_('Integrations|Enable SSL verification'),
+ help: s_('Integrations|Clear if using a self-signed certificate.')
+ )
+ )
end
end
end
diff --git a/app/models/concerns/integrations/reset_secret_fields.rb b/app/models/concerns/integrations/reset_secret_fields.rb
index f79c4392f19..24d716fe5dd 100644
--- a/app/models/concerns/integrations/reset_secret_fields.rb
+++ b/app/models/concerns/integrations/reset_secret_fields.rb
@@ -12,9 +12,7 @@ module Integrations
end
def exposing_secrets_fields
- # TODO: Once all integrations use `Integrations::Field` we can remove the `.try` here.
- # See: https://gitlab.com/groups/gitlab-org/-/epics/7652
- fields.select { _1.try(:exposes_secrets) }.pluck(:name)
+ fields.select(&:exposes_secrets).pluck(:name)
end
private
diff --git a/app/models/concerns/integrations/slack_mattermost_fields.rb b/app/models/concerns/integrations/slack_mattermost_fields.rb
new file mode 100644
index 00000000000..a8e63c4e405
--- /dev/null
+++ b/app/models/concerns/integrations/slack_mattermost_fields.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Integrations
+ module SlackMattermostFields
+ extend ActiveSupport::Concern
+
+ included do
+ field :webhook,
+ help: -> { webhook_help },
+ required: true,
+ if: -> { requires_webhook? }
+
+ field :username,
+ placeholder: 'GitLab-integration',
+ if: -> { requires_webhook? }
+
+ field :notify_only_broken_pipelines,
+ type: :checkbox,
+ section: Integration::SECTION_TYPE_CONFIGURATION,
+ help: 'Do not send notifications for successful pipelines.'
+
+ field :branches_to_be_notified,
+ type: :select,
+ section: Integration::SECTION_TYPE_CONFIGURATION,
+ title: -> { s_('Integration|Branches for which notifications are to be sent') },
+ choices: -> { branch_choices }
+
+ field :labels_to_be_notified,
+ section: Integration::SECTION_TYPE_CONFIGURATION,
+ placeholder: '~backend,~frontend',
+ help: 'Send notifications for issue, merge request, and comment events with the listed labels only. ' \
+ 'Leave blank to receive notifications for all events.'
+
+ field :labels_to_be_notified_behavior,
+ type: :select,
+ section: Integration::SECTION_TYPE_CONFIGURATION,
+ choices: [
+ ['Match any of the labels', Integrations::BaseChatNotification::MATCH_ANY_LABEL],
+ ['Match all of the labels', Integrations::BaseChatNotification::MATCH_ALL_LABELS]
+ ]
+ end
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 9a513ea0e5b..a9a00ab1c44 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -19,6 +19,7 @@ module Issuable
include Awardable
include Taskable
include Importable
+ include Transitionable
include Editable
include AfterCommitQueue
include Sortable
@@ -33,7 +34,7 @@ module Issuable
TITLE_HTML_LENGTH_MAX = 800
DESCRIPTION_LENGTH_MAX = 1.megabyte
DESCRIPTION_HTML_LENGTH_MAX = 5.megabytes
- SEARCHABLE_FIELDS = %w(title description).freeze
+ SEARCHABLE_FIELDS = %w[title description].freeze
MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS = 200
STATE_ID_MAP = {
@@ -225,6 +226,10 @@ module Issuable
false
end
+ def supports_lock_on_merge?
+ false
+ end
+
def severity
return IssuableSeverity::DEFAULT unless supports_severity?
@@ -235,6 +240,10 @@ module Issuable
super + [:notes]
end
+ def importing_or_transitioning?
+ importing? || transitioning?
+ end
+
private
def validate_description_length?
@@ -408,14 +417,14 @@ module Issuable
sort = sort.to_s
grouping_columns = [arel_table[:id]]
- if %w(milestone_due_desc milestone_due_asc milestone).include?(sort)
+ if %w[milestone_due_desc milestone_due_asc milestone].include?(sort)
milestone_table = Milestone.arel_table
grouping_columns << milestone_table[:id]
grouping_columns << milestone_table[:due_date]
- elsif %w(merged_at_desc merged_at_asc merged_at).include?(sort)
+ elsif %w[merged_at_desc merged_at_asc merged_at].include?(sort)
grouping_columns << MergeRequest::Metrics.arel_table[:id]
grouping_columns << MergeRequest::Metrics.arel_table[:merged_at]
- elsif %w(closed_at_desc closed_at_asc closed_at).include?(sort)
+ elsif %w[closed_at_desc closed_at_asc closed_at].include?(sort)
grouping_columns << MergeRequest::Metrics.arel_table[:id]
grouping_columns << MergeRequest::Metrics.arel_table[:latest_closed_at]
end
diff --git a/app/models/concerns/issue_available_features.rb b/app/models/concerns/issue_available_features.rb
index 3f65e701da7..2969f1e1928 100644
--- a/app/models/concerns/issue_available_features.rb
+++ b/app/models/concerns/issue_available_features.rb
@@ -10,10 +10,10 @@ module IssueAvailableFeatures
# EE only features are listed on EE::IssueAvailableFeatures
def available_features_for_issue_types
{
- assignee: %w(issue incident),
- confidentiality: %w(issue incident),
- time_tracking: %w(issue incident),
- move_and_clone: %w(issue incident)
+ assignee: %w[issue incident],
+ confidentiality: %w[issue incident objective key_result],
+ time_tracking: %w[issue incident],
+ move_and_clone: %w[issue incident]
}.with_indifferent_access
end
end
diff --git a/app/models/concerns/linkable_item.rb b/app/models/concerns/linkable_item.rb
index 135252727ab..c91e3615ba7 100644
--- a/app/models/concerns/linkable_item.rb
+++ b/app/models/concerns/linkable_item.rb
@@ -16,6 +16,7 @@ module LinkableItem
scope :for_source, ->(item) { where(source_id: item.id) }
scope :for_target, ->(item) { where(target_id: item.id) }
+ scope :for_source_and_target, ->(source, target) { where(source: source, target: target) }
scope :for_items, ->(source, target) do
where(source: source, target: target).or(where(source: target, target: source))
end
diff --git a/app/models/concerns/mentionable/reference_regexes.rb b/app/models/concerns/mentionable/reference_regexes.rb
index 0b6075fbeb8..b5634ba3b6d 100644
--- a/app/models/concerns/mentionable/reference_regexes.rb
+++ b/app/models/concerns/mentionable/reference_regexes.rb
@@ -28,7 +28,7 @@ module Mentionable
def self.external_pattern
strong_memoize(:external_pattern) do
issue_pattern = Integrations::BaseIssueTracker.base_reference_pattern
- link_patterns = URI::DEFAULT_PARSER.make_regexp(%w(http https))
+ link_patterns = URI::DEFAULT_PARSER.make_regexp(%w[http https])
reference_pattern(link_patterns, issue_pattern)
end
end
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index 40a91c8ac94..06cee46645b 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -12,17 +12,17 @@ module Noteable
class_methods do
# `Noteable` class names that support replying to individual notes.
def replyable_types
- %w(Issue MergeRequest)
+ %w[Issue MergeRequest]
end
# `Noteable` class names that support resolvable notes.
def resolvable_types
- %w(Issue MergeRequest DesignManagement::Design)
+ %w[Issue MergeRequest DesignManagement::Design]
end
# `Noteable` class names that support creating/forwarding individual notes.
def email_creatable_types
- %w(Issue)
+ %w[Issue]
end
end
@@ -164,28 +164,15 @@ module Noteable
[MergeRequest, Issue].include?(self.class)
end
- def etag_caching_enabled?
+ def real_time_notes_enabled?
false
end
- def expire_note_etag_cache
+ def broadcast_notes_changed
return unless discussions_rendered_on_frontend?
- return unless etag_caching_enabled?
+ return unless real_time_notes_enabled?
- # TODO: We need to figure out a way to make ETag caching work for group-level work items
- Gitlab::EtagCaching::Store.new.touch(note_etag_key) unless is_a?(Issue) && project.nil?
-
- Noteable::NotesChannel.broadcast_to(self, event: 'updated') if Feature.enabled?(:action_cable_notes, project || try(:group))
- end
-
- def note_etag_key
- return Gitlab::Routing.url_helpers.designs_project_issue_path(project, issue, { vueroute: filename }) if self.is_a?(DesignManagement::Design)
-
- Gitlab::Routing.url_helpers.project_noteable_notes_path(
- project,
- target_type: noteable_target_type_name,
- target_id: id
- )
+ Noteable::NotesChannel.broadcast_to(self, event: 'updated')
end
def after_note_created(_note)
diff --git a/app/models/concerns/packages/nuget/version_normalizable.rb b/app/models/concerns/packages/nuget/version_normalizable.rb
index 473e5f07811..4bcfec89570 100644
--- a/app/models/concerns/packages/nuget/version_normalizable.rb
+++ b/app/models/concerns/packages/nuget/version_normalizable.rb
@@ -13,7 +13,7 @@ module Packages
private
def set_normalized_version
- return unless package && Feature.enabled?(:nuget_normalized_version, package.project)
+ return unless package
self.normalized_version = normalize
end
diff --git a/app/models/concerns/pg_full_text_searchable.rb b/app/models/concerns/pg_full_text_searchable.rb
index 562c8cf23f3..b7ca6f61573 100644
--- a/app/models/concerns/pg_full_text_searchable.rb
+++ b/app/models/concerns/pg_full_text_searchable.rb
@@ -21,11 +21,11 @@
module PgFullTextSearchable
extend ActiveSupport::Concern
- LONG_WORDS_REGEX = %r([A-Za-z0-9+/@]{50,}).freeze
+ LONG_WORDS_REGEX = %r([A-Za-z0-9+/@]{50,})
TSVECTOR_MAX_LENGTH = 1.megabyte.freeze
TEXT_SEARCH_DICTIONARY = 'english'
- URL_SCHEME_REGEX = %r{(?<=\A|\W)\w+://(?=\w+)}.freeze
- TSQUERY_DISALLOWED_CHARACTERS_REGEX = %r{[^a-zA-Z0-9 .@/\-_"]}.freeze
+ URL_SCHEME_REGEX = %r{(?<=\A|\W)\w+://(?=\w+)}
+ TSQUERY_DISALLOWED_CHARACTERS_REGEX = %r{[^a-zA-Z0-9 .@/\-_"]}
def update_search_data!
tsvector_sql_nodes = self.class.pg_full_text_searchable_columns.map do |column, weight|
diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb
index a87eadb9332..ea8a1640bea 100644
--- a/app/models/concerns/protected_ref.rb
+++ b/app/models/concerns/protected_ref.rb
@@ -3,6 +3,8 @@
module ProtectedRef
extend ActiveSupport::Concern
+ include Importable
+
included do
belongs_to :project, touch: true
@@ -32,12 +34,13 @@ module ProtectedRef
# to fail.
has_many :"#{type}_access_levels", inverse_of: self.model_name.singular
+ # Overridden in EE with `if: -> { false }` so this validation does not apply on an EE instance.
validates :"#{type}_access_levels",
length: {
is: 1,
message: "are restricted to a single instance per #{self.model_name.human}."
},
- unless: -> { allow_multiple?(type) }
+ unless: -> { allow_multiple?(type) || importing? }
accepts_nested_attributes_for :"#{type}_access_levels", allow_destroy: true
end
diff --git a/app/models/concerns/redactable.rb b/app/models/concerns/redactable.rb
index 53ae300ee2d..5ad96d6cc46 100644
--- a/app/models/concerns/redactable.rb
+++ b/app/models/concerns/redactable.rb
@@ -10,7 +10,7 @@
module Redactable
extend ActiveSupport::Concern
- UNSUBSCRIBE_PATTERN = %r{/sent_notifications/\h{32}/unsubscribe}.freeze
+ UNSUBSCRIBE_PATTERN = %r{/sent_notifications/\h{32}/unsubscribe}
class_methods do
def redact_field(field)
diff --git a/app/models/concerns/require_email_verification.rb b/app/models/concerns/require_email_verification.rb
index d7182778b36..6581928f637 100644
--- a/app/models/concerns/require_email_verification.rb
+++ b/app/models/concerns/require_email_verification.rb
@@ -7,10 +7,7 @@ module RequireEmailVerification
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
- # This value is twice the amount we want it to be, because due to a bug in the devise-two-factor
- # gem every failed login attempt increments the value of failed_attempts by 2 instead of 1.
- # See: https://github.com/tinfoil/devise-two-factor/issues/127
- MAXIMUM_ATTEMPTS = 3 * 2
+ MAXIMUM_ATTEMPTS = 3
UNLOCK_IN = 24.hours
included do
diff --git a/app/models/concerns/resolvable_discussion.rb b/app/models/concerns/resolvable_discussion.rb
index e967c78154d..5c2f0aa04ac 100644
--- a/app/models/concerns/resolvable_discussion.rb
+++ b/app/models/concerns/resolvable_discussion.rb
@@ -116,7 +116,7 @@ module ResolvableDiscussion
# Set the notes array to the updated notes
@notes = notes_relation.fresh.to_a # rubocop:disable Gitlab/ModuleWithInstanceVariables
- noteable.expire_note_etag_cache
+ noteable.broadcast_notes_changed
clear_memoized_values
end
diff --git a/app/models/concerns/resolvable_note.rb b/app/models/concerns/resolvable_note.rb
index 7f9a7faa3f5..23abc5d5c22 100644
--- a/app/models/concerns/resolvable_note.rb
+++ b/app/models/concerns/resolvable_note.rb
@@ -4,7 +4,7 @@ module ResolvableNote
extend ActiveSupport::Concern
# Names of all subclasses of `Note` that can be resolvable.
- RESOLVABLE_TYPES = %w(DiffNote DiscussionNote).freeze
+ RESOLVABLE_TYPES = %w[DiffNote DiscussionNote].freeze
included do
belongs_to :resolved_by, class_name: "User"
diff --git a/app/models/concerns/restricted_signup.rb b/app/models/concerns/restricted_signup.rb
index cf97be21165..6af9ede5e8b 100644
--- a/app/models/concerns/restricted_signup.rb
+++ b/app/models/concerns/restricted_signup.rb
@@ -84,3 +84,5 @@ module RestrictedSignup
end
end
end
+
+::RestrictedSignup.prepend_mod
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index f2badfe48dd..ef14ff5fbe2 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -14,7 +14,17 @@ module Routable
# Routable.find_by_full_path('groupname/projectname') # -> Project
#
# Returns a single object, or nil.
- def self.find_by_full_path(path, follow_redirects: false, route_scope: Route, redirect_route_scope: RedirectRoute)
+
+ # rubocop:disable Metrics/CyclomaticComplexity
+ # rubocop:disable Metrics/PerceivedComplexity
+ def self.find_by_full_path(
+ path,
+ follow_redirects: false,
+ route_scope: Route,
+ redirect_route_scope: RedirectRoute,
+ optimize_routable: Routable.optimize_routable_enabled?
+ )
+
return unless path.present?
# Convert path to string to prevent DB error: function lower(integer) does not exist
@@ -25,20 +35,50 @@ module Routable
#
# We need to qualify the columns with the table name, to support both direct lookups on
# Route/RedirectRoute, and scoped lookups through the Routable classes.
- Gitlab::Database.allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046") do
+ if optimize_routable
+ path_condition = { path: path }
+
+ source_type_condition = if route_scope == Route
+ {}
+ else
+ { source_type: route_scope.klass.base_class }
+ end
+
route =
- route_scope.find_by(routes: { path: path }) ||
- route_scope.iwhere(Route.arel_table[:path] => path).take
+ Route.where(source_type_condition).find_by(path_condition) ||
+ Route.where(source_type_condition).iwhere(path_condition).take
if follow_redirects
- route ||= redirect_route_scope.iwhere(RedirectRoute.arel_table[:path] => path).take
+ route ||= RedirectRoute.where(source_type_condition).iwhere(path_condition).take
end
- next unless route
+ return unless route
+ return route.source if route_scope == Route
+
+ route_scope.find_by(id: route.source_id)
+ else
+ Gitlab::Database.allow_cross_joins_across_databases(url:
+ "https://gitlab.com/gitlab-org/gitlab/-/issues/420046") do
+ route =
+ route_scope.find_by(routes: { path: path }) ||
+ route_scope.iwhere(Route.arel_table[:path] => path).take
+
+ if follow_redirects
+ route ||= redirect_route_scope.iwhere(RedirectRoute.arel_table[:path] => path).take
+ end
- route.is_a?(Routable) ? route : route.source
+ next unless route
+
+ route.is_a?(Routable) ? route : route.source
+ end
end
end
+ # rubocop:enable Metrics/PerceivedComplexity
+ # rubocop:enable Metrics/CyclomaticComplexity
+
+ def self.optimize_routable_enabled?
+ Feature.enabled?(:optimize_routable)
+ end
included do
# Remove `inverse_of: source` when upgraded to rails 5.2
@@ -67,13 +107,22 @@ module Routable
#
# Returns a single object, or nil.
def find_by_full_path(path, follow_redirects: false)
- # TODO: Optimize these queries by avoiding joins
- # https://gitlab.com/gitlab-org/gitlab/-/issues/292252
+ optimize_routable = Routable.optimize_routable_enabled?
+
+ if optimize_routable
+ route_scope = all
+ redirect_route_scope = RedirectRoute
+ else
+ route_scope = includes(:route).references(:routes)
+ redirect_route_scope = joins(:redirect_routes)
+ end
+
Routable.find_by_full_path(
path,
follow_redirects: follow_redirects,
- route_scope: includes(:route).references(:routes),
- redirect_route_scope: joins(:redirect_routes)
+ route_scope: route_scope,
+ redirect_route_scope: redirect_route_scope,
+ optimize_routable: optimize_routable
)
end
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index b73ed937b5d..5455a2159cd 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -17,8 +17,6 @@ module Storage
Namespace.find(parent_id_before_last_save) # raise NotFound early if needed
end
- move_repositories
-
if saved_change_to_parent?
former_parent_full_path = parent_was&.full_path
parent_full_path = parent&.full_path
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index bf645e99b5e..96f684522d2 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -11,8 +11,8 @@ require 'task_list/filter'
module Taskable
COMPLETED = 'completed'
INCOMPLETE = 'incomplete'
- COMPLETE_PATTERN = /\[[xX]\]/.freeze
- INCOMPLETE_PATTERN = /\[[[:space:]]\]/.freeze
+ COMPLETE_PATTERN = /\[[xX]\]/
+ INCOMPLETE_PATTERN = /\[[[:space:]]\]/
ITEM_PATTERN = %r{
^
(?:(?:>\s{0,4})*) # optional blockquote characters
@@ -22,7 +22,7 @@ module Taskable
#{COMPLETE_PATTERN}|#{INCOMPLETE_PATTERN}
)
(\s.+) # followed by whitespace and some text.
- }x.freeze
+ }x
ITEM_PATTERN_UNTRUSTED =
'^' \
diff --git a/app/models/concerns/transitionable.rb b/app/models/concerns/transitionable.rb
new file mode 100644
index 00000000000..70e1fc8b78a
--- /dev/null
+++ b/app/models/concerns/transitionable.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Transitionable
+ extend ActiveSupport::Concern
+
+ attr_accessor :transitioning
+
+ def transitioning?
+ return false unless transitioning && Feature.enabled?(:skip_validations_during_transitions, project)
+
+ true
+ end
+
+ def enable_transitioning
+ self.transitioning = true
+ end
+
+ def disable_transitioning
+ self.transitioning = false
+ end
+end
diff --git a/app/models/concerns/users/visitable.rb b/app/models/concerns/users/visitable.rb
new file mode 100644
index 00000000000..cb8e5fdc682
--- /dev/null
+++ b/app/models/concerns/users/visitable.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Users
+ module Visitable
+ extend ActiveSupport::Concern
+
+ included do
+ def self.visited_around?(entity_id:, user_id:, time:)
+ visits_around(entity_id: entity_id, user_id: user_id, time: time).any?
+ end
+
+ def self.visits_around(entity_id:, user_id:, time:)
+ time = time.to_datetime
+ where(entity_id: entity_id, user_id: user_id, visited_at: (time - 15.minutes)..(time + 15.minutes))
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/with_uploads.rb b/app/models/concerns/with_uploads.rb
index caaf2b33ef0..319509ea69a 100644
--- a/app/models/concerns/with_uploads.rb
+++ b/app/models/concerns/with_uploads.rb
@@ -22,7 +22,7 @@ module WithUploads
# Currently there is no simple way how to select only not-mounted
# uploads, it should be all FileUploaders so we select them by
# `uploader` class
- FILE_UPLOADERS = %w(PersonalFileUploader NamespaceFileUploader FileUploader).freeze
+ FILE_UPLOADERS = %w[PersonalFileUploader NamespaceFileUploader FileUploader].freeze
included do
around_destroy :ignore_uploads_table_in_transaction
diff --git a/app/models/container_expiration_policy.rb b/app/models/container_expiration_policy.rb
index aecb47f7a03..f643fa7730b 100644
--- a/app/models/container_expiration_policy.rb
+++ b/app/models/container_expiration_policy.rb
@@ -80,7 +80,9 @@ class ContainerExpirationPolicy < ApplicationRecord
end
def set_next_run_at
- self.next_run_at = Time.zone.now + ChronicDuration.parse(cadence).seconds
+ cadence_seconds = ChronicDuration.parse(cadence, use_complete_matcher: true).seconds
+
+ self.next_run_at = Time.zone.now + cadence_seconds
end
def disable!
diff --git a/app/models/container_registry/event.rb b/app/models/container_registry/event.rb
index dd2675e17d8..9f7724c052c 100644
--- a/app/models/container_registry/event.rb
+++ b/app/models/container_registry/event.rb
@@ -4,25 +4,25 @@ module ContainerRegistry
class Event
include Gitlab::Utils::StrongMemoize
- ALLOWED_ACTIONS = %w(push delete).freeze
+ ALLOWED_ACTIONS = %w[push delete].freeze
PUSH_ACTION = 'push'
DELETE_ACTION = 'delete'
EVENT_TRACKING_CATEGORY = 'container_registry:notification'
EVENT_PREFIX = 'i_container_registry'
- ALLOWED_ACTOR_TYPES = %w(
+ ALLOWED_ACTOR_TYPES = %w[
personal_access_token
build
gitlab_or_ldap
- ).freeze
+ ].freeze
- TRACKABLE_ACTOR_EVENTS = %w(
+ TRACKABLE_ACTOR_EVENTS = %w[
push_tag
delete_tag
push_repository
delete_repository
create_repository
- ).freeze
+ ].freeze
attr_reader :event
@@ -60,7 +60,7 @@ module ContainerRegistry
def target_tag?
# There is no clear indication in the event structure when we delete a top-level manifest
- # except existance of "tag" key
+ # except existence of "tag" key
event['target'].has_key?('tag')
end
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
index 625d68925c6..c704795130b 100644
--- a/app/models/custom_emoji.rb
+++ b/app/models/custom_emoji.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class CustomEmoji < ApplicationRecord
- NAME_REGEXP = /[a-z0-9_-]+/.freeze
+ NAME_REGEXP = /[a-z0-9_-]+/
belongs_to :namespace, inverse_of: :custom_emoji
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index f9fa4bd212c..de777b8ae53 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -43,7 +43,7 @@ class DeployKey < Key
end
def user
- super || User.ghost
+ super || Users::Internal.ghost
end
def audit_details
diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb
index 498ca9c4f30..920321a1699 100644
--- a/app/models/deploy_token.rb
+++ b/app/models/deploy_token.rb
@@ -8,8 +8,8 @@ class DeployToken < ApplicationRecord
add_authentication_token_field :token, encrypted: :required
- AVAILABLE_SCOPES = %i(read_repository read_registry write_registry
- read_package_registry write_package_registry).freeze
+ AVAILABLE_SCOPES = %i[read_repository read_registry write_registry
+ read_package_registry write_package_registry].freeze
GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'
REQUIRED_DEPENDENCY_PROXY_SCOPES = %i[read_registry write_registry].freeze
diff --git a/app/models/description_version.rb b/app/models/description_version.rb
index fb61b7f5fde..05cca9f931f 100644
--- a/app/models/description_version.rb
+++ b/app/models/description_version.rb
@@ -9,7 +9,7 @@ class DescriptionVersion < ApplicationRecord
delegate :resource_parent, to: :issuable
def self.issuable_attrs
- %i(issue merge_request).freeze
+ %i[issue merge_request].freeze
end
def issuable
diff --git a/app/models/design_management.rb b/app/models/design_management.rb
index 81e170f7e59..20ada71755b 100644
--- a/app/models/design_management.rb
+++ b/app/models/design_management.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module DesignManagement
- DESIGN_IMAGE_SIZES = %w(v432x230).freeze
+ DESIGN_IMAGE_SIZES = %w[v432x230].freeze
def self.designs_directory
'designs'
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 02979d5f804..d680d0e334f 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -9,7 +9,7 @@ class DiffNote < Note
include Gitlab::Utils::StrongMemoize
def self.noteable_types
- %w(MergeRequest Commit DesignManagement::Design)
+ %w[MergeRequest Commit DesignManagement::Design]
end
validates :original_position, presence: true
diff --git a/app/models/discussion_note.rb b/app/models/discussion_note.rb
index 6621b30b645..a1dfa0e72ec 100644
--- a/app/models/discussion_note.rb
+++ b/app/models/discussion_note.rb
@@ -9,7 +9,7 @@ class DiscussionNote < Note
# Names of all implementers of `Noteable` that support discussions.
def self.noteable_types
- %w(MergeRequest Issue Commit Snippet)
+ %w[MergeRequest Issue Commit Snippet]
end
validates :noteable_type, inclusion: { in: noteable_types }
diff --git a/app/models/draft_note.rb b/app/models/draft_note.rb
index ffc04f9bf90..f95eec742d8 100644
--- a/app/models/draft_note.rb
+++ b/app/models/draft_note.rb
@@ -5,8 +5,8 @@ class DraftNote < ApplicationRecord
include Sortable
include ShaAttribute
- PUBLISH_ATTRS = %i(noteable_id noteable_type type note).freeze
- DIFF_ATTRS = %i(position original_position change_position commit_id).freeze
+ PUBLISH_ATTRS = %i[noteable_id noteable_type type note].freeze
+ DIFF_ATTRS = %i[position original_position change_position commit_id].freeze
sha_attribute :commit_id
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 36445279b86..29394c37e2c 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -25,7 +25,6 @@ class Environment < ApplicationRecord
has_many :successful_deployments, -> { success }, class_name: 'Deployment'
has_many :active_deployments, -> { active }, class_name: 'Deployment'
has_many :prometheus_alerts, inverse_of: :environment
- has_many :self_managed_prometheus_alert_events, inverse_of: :environment
has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :environment
# NOTE: If you preload multiple last deployments of environments, use Preloaders::Environments::DeploymentPreloader.
@@ -108,11 +107,11 @@ class Environment < ApplicationRecord
scope :deployed_and_updated_before, -> (project_id, before) do
# this query joins deployments and filters out any environment that has recent deployments
- joins = %{
+ joins = %(
LEFT JOIN "deployments" on "deployments".environment_id = "environments".id
AND "deployments".project_id = #{project_id}
AND "deployments".updated_at >= #{connection.quote(before)}
- }
+ )
Environment.joins(joins)
.where(project_id: project_id, updated_at: ...before)
.group('id', 'deployments.id')
@@ -193,7 +192,7 @@ class Environment < ApplicationRecord
end
event :stop_complete do
- transition %i(available stopping) => :stopped
+ transition %i[available stopping] => :stopped
end
state :available
diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb
index 7687bc2be60..f31615f2b3b 100644
--- a/app/models/environment_status.rb
+++ b/app/models/environment_status.rb
@@ -79,7 +79,7 @@ class EnvironmentStatus
private
- PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze
+ PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i
def deployment_metrics
@deployment_metrics ||= DeploymentMetrics.new(project, deployment)
@@ -102,7 +102,6 @@ class EnvironmentStatus
return [] unless pipeline
environments = pipeline.environments_in_self_and_project_descendants.includes(:project)
- environments = environments.available if Feature.disabled?(:review_apps_redeploy_mr_widget, mr.project)
environments.map do |environment|
next unless Ability.allowed?(user, :read_environment, environment)
diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb
index c52f8a58c00..318538be645 100644
--- a/app/models/error_tracking/project_error_tracking_setting.rb
+++ b/app/models/error_tracking/project_error_tracking_setting.rb
@@ -19,7 +19,7 @@ module ErrorTracking
(?<project>[^/]+)/*
)?
\z
- }x.freeze
+ }x
self.reactive_cache_key = ->(setting) { [setting.class.model_name.singular, setting.project_id] }
self.reactive_cache_work_type = :external_dependency
diff --git a/app/models/event.rb b/app/models/event.rb
index 4547d7b9e60..9e4a662aaa5 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -10,6 +10,7 @@ class Event < ApplicationRecord
include UsageStatistics
include ShaAttribute
include IgnorableColumns
+ include EachBatch
ignore_column :target_id_convert_to_bigint, remove_with: '16.4', remove_after: '2023-09-22'
@@ -69,7 +70,7 @@ class Event < ApplicationRecord
# If the association for "target" defines an "author" association we want to
# eager-load this so Banzai & friends don't end up performing N+1 queries to
# get the authors of notes, issues, etc. (likewise for "noteable").
- incs = %i(author noteable work_item_type).select do |a|
+ incs = %i[author noteable work_item_type].select do |a|
reflections['events'].active_record.reflect_on_association(a)
end
@@ -137,7 +138,7 @@ class Event < ApplicationRecord
where(
'action IN (?) OR (target_type IN (?) AND action IN (?))',
[actions[:pushed], actions[:commented]],
- %w(MergeRequest Issue WorkItem), [actions[:created], actions[:closed], actions[:merged]]
+ %w[MergeRequest Issue WorkItem], [actions[:created], actions[:closed], actions[:merged]]
)
end
diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb
index 1bf35179393..f0cae9c88ca 100644
--- a/app/models/gpg_key.rb
+++ b/app/models/gpg_key.rb
@@ -10,7 +10,7 @@ class GpgKey < ApplicationRecord
sha_attribute :fingerprint
belongs_to :user
- has_many :gpg_signatures
+ has_many :gpg_signatures, class_name: 'CommitSignatures::GpgSignature'
has_many :subkeys, class_name: 'GpgKeySubkey'
scope :with_subkeys, -> { includes(:subkeys) }
diff --git a/app/models/group.rb b/app/models/group.rb
index 9df3c143e0c..9330ffef156 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -35,11 +35,12 @@ class Group < Namespace
foreign_key: :member_namespace_id, inverse_of: :group, class_name: 'GroupMember'
alias_method :members, :group_members
- has_many :users, through: :group_members
- has_many :owners,
- -> { where(members: { access_level: Gitlab::Access::OWNER }) },
- through: :all_group_members,
- source: :user
+ has_many :users, -> { allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/422405") },
+ through: :group_members
+ has_many :owners, -> {
+ where(members: { access_level: Gitlab::Access::OWNER })
+ .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/422405")
+ }, through: :all_group_members, source: :user
has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
has_many :namespace_requesters, -> { where.not(requested_at: nil).unscope(where: %i[source_id source_type]) },
@@ -785,8 +786,6 @@ class Group < Namespace
end
def execute_integrations(data, hooks_scope)
- return unless Feature.enabled?(:group_mentions, self)
-
integrations.public_send(hooks_scope).each do |integration| # rubocop:disable GitlabSecurity/PublicSend
integration.async_execute(data)
end
@@ -800,7 +799,9 @@ class Group < Namespace
end
def first_owner
- owners.first || parent&.first_owner || owner
+ first_owner_member = all_group_members.all_owners.order(:user_id).first
+
+ first_owner_member&.user || parent&.first_owner || owner
end
def default_branch_name
@@ -898,6 +899,10 @@ class Group < Namespace
feature_flag_enabled_for_self_or_ancestor?(:linked_work_items)
end
+ def supports_lock_on_merge?
+ feature_flag_enabled_for_self_or_ancestor?(:enforce_locked_labels_on_merge, type: :ops)
+ end
+
def usage_quotas_enabled?
::Feature.enabled?(:usage_quotas_for_all_editions, self) && root?
end
@@ -939,12 +944,12 @@ class Group < Namespace
private
- def feature_flag_enabled_for_self_or_ancestor?(feature_flag)
+ def feature_flag_enabled_for_self_or_ancestor?(feature_flag, type: :development)
actors = [root_ancestor]
actors << self if root_ancestor != self
actors.any? do |actor|
- ::Feature.enabled?(feature_flag, actor)
+ ::Feature.enabled?(feature_flag, actor, type: type)
end
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index d7a95363337..c0bfe31fb38 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -103,7 +103,7 @@ class WebHook < ApplicationRecord
end
# See app/validators/json_schemas/web_hooks_url_variables.json
- VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/.freeze
+ VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/
def interpolated_url(url = self.url, url_variables = self.url_variables)
return url unless url.include?('{')
diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb
index 4c35f699468..3e0c8e7c472 100644
--- a/app/models/hooks/web_hook_log.rb
+++ b/app/models/hooks/web_hook_log.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class WebHookLog < ApplicationRecord
- include SafeUrl
include Presentable
include DeleteWithLimit
include CreatedAtFilterable
@@ -58,10 +57,18 @@ class WebHookLog < ApplicationRecord
self[:request_headers].merge('X-Gitlab-Token' => _('[REDACTED]'))
end
+ def url_current?
+ # URL hash hasn't been set, so we must assume there's no prior value to
+ # compare to.
+ return true if url_hash.nil?
+
+ Gitlab::CryptoHelper.sha256(web_hook.interpolated_url) == url_hash
+ end
+
private
def obfuscate_basic_auth
- self.url = safe_url
+ self.url = Gitlab::UrlSanitizer.sanitize_masked_url(url)
end
def redact_user_emails
diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb
index 57638356362..7b2036a9def 100644
--- a/app/models/instance_configuration.rb
+++ b/app/models/instance_configuration.rb
@@ -3,7 +3,7 @@
require 'resolv'
class InstanceConfiguration
- SSH_ALGORITHMS = %w(DSA ECDSA ED25519 RSA).freeze
+ SSH_ALGORITHMS = %w[DSA ECDSA ED25519 RSA].freeze
SSH_ALGORITHMS_PATH = '/etc/ssh/'
CACHE_KEY = 'instance_configuration'
EXPIRATION_TIME = 24.hours
diff --git a/app/models/integration.rb b/app/models/integration.rb
index bc86b08018f..d4c76f743a3 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -63,6 +63,7 @@ class Integration < ApplicationRecord
encode: false,
encode_iv: false
+ alias_attribute :name, :title
# Handle assignment of props with symbol keys.
# To do this correctly, we need to call the method generated by attr_encrypted.
alias_method :attr_encrypted_props=, :properties=
@@ -468,11 +469,8 @@ class Integration < ApplicationRecord
[]
end
- # TODO: Once all integrations use `Integrations::Field` we can
- # use `#secret?` here.
- # See: https://gitlab.com/groups/gitlab-org/-/epics/7652
def secret_fields
- fields.select { |f| f[:type] == :password }.pluck(:name)
+ fields.select(&:secret?).pluck(:name)
end
# Expose a list of fields in the JSON endpoint.
diff --git a/app/models/integrations/apple_app_store.rb b/app/models/integrations/apple_app_store.rb
index 6f96626718f..ef12fc6bf6f 100644
--- a/app/models/integrations/apple_app_store.rb
+++ b/app/models/integrations/apple_app_store.rb
@@ -4,8 +4,8 @@ require 'app_store_connect'
module Integrations
class AppleAppStore < Integration
- ISSUER_ID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/.freeze
- KEY_ID_REGEX = /\A(?=.*[A-Z])(?=.*[0-9])[A-Z0-9]+\z/.freeze
+ ISSUER_ID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/
+ KEY_ID_REGEX = /\A(?=.*[A-Z])(?=.*[0-9])[A-Z0-9]+\z/
IS_KEY_CONTENT_BASE64 = "true"
SECTION_TYPE_APPLE_APP_STORE = 'apple_app_store'
diff --git a/app/models/integrations/asana.rb b/app/models/integrations/asana.rb
index 7436c08aa38..859522670ef 100644
--- a/app/models/integrations/asana.rb
+++ b/app/models/integrations/asana.rb
@@ -12,8 +12,7 @@ module Integrations
help: -> { s_('AsanaService|User Personal Access Token. User must have access to the task. All comments are attributed to this user.') },
non_empty_password_title: -> { s_('ProjectService|Enter new API key') },
non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current API key.') },
- # Example Personal Access Token from Asana docs
- placeholder: '0/68a9e79b868c6789e79a124c30b0',
+ placeholder: '0/68a9e79b868c6789e79a124c30b0', # Example Personal Access Token from Asana docs
required: true
field :restrict_to_branch,
@@ -38,7 +37,7 @@ module Integrations
end
def self.supported_events
- %w(push)
+ %w[push]
end
def client
diff --git a/app/models/integrations/assembla.rb b/app/models/integrations/assembla.rb
index 6831fac32e6..1d3616b4c3b 100644
--- a/app/models/integrations/assembla.rb
+++ b/app/models/integrations/assembla.rb
@@ -28,7 +28,7 @@ module Integrations
end
def self.supported_events
- %w(push)
+ %w[push]
end
def execute(data)
diff --git a/app/models/integrations/base_chat_notification.rb b/app/models/integrations/base_chat_notification.rb
index 4d207574ca7..2c929dc2cb3 100644
--- a/app/models/integrations/base_chat_notification.rb
+++ b/app/models/integrations/base_chat_notification.rb
@@ -31,12 +31,12 @@ module Integrations
# Custom serialized properties initialization
prop_accessor(*SUPPORTED_EVENTS.map { |event| EVENT_CHANNEL[event] })
- boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch
+ boolean_accessor :notify_only_default_branch
validates :webhook,
presence: true,
public_url: true,
- if: -> (integration) { integration.activated? && integration.requires_webhook? }
+ if: -> (integration) { integration.activated? && integration.class.requires_webhook? }
validates :labels_to_be_notified_behavior, inclusion: { in: LABEL_NOTIFICATION_BEHAVIOURS }, allow_blank: true, if: :activated?
validate :validate_channel_limit, if: :activated?
@@ -44,7 +44,7 @@ module Integrations
super
if properties.empty?
- self.notify_only_broken_pipelines = true
+ self.notify_only_broken_pipelines = true if self.respond_to?(:notify_only_broken_pipelines)
self.branches_to_be_notified = "default"
self.labels_to_be_notified_behavior = MATCH_ANY_LABEL
elsif !self.notify_only_default_branch.nil?
@@ -72,48 +72,7 @@ module Integrations
end
def fields
- default_fields + build_event_channels
- end
-
- def default_fields
- [
- {
- type: :checkbox,
- section: SECTION_TYPE_CONFIGURATION,
- name: 'notify_only_broken_pipelines',
- help: 'Do not send notifications for successful pipelines.'
- }.freeze,
- {
- type: :select,
- section: SECTION_TYPE_CONFIGURATION,
- name: 'branches_to_be_notified',
- title: s_('Integrations|Branches for which notifications are to be sent'),
- choices: self.class.branch_choices
- }.freeze,
- {
- type: :text,
- section: SECTION_TYPE_CONFIGURATION,
- name: 'labels_to_be_notified',
- placeholder: '~backend,~frontend',
- help: 'Send notifications for issue, merge request, and comment events with the listed labels only. Leave blank to receive notifications for all events.'
- }.freeze,
- {
- type: :select,
- section: SECTION_TYPE_CONFIGURATION,
- name: 'labels_to_be_notified_behavior',
- choices: [
- ['Match any of the labels', MATCH_ANY_LABEL],
- ['Match all of the labels', MATCH_ALL_LABELS]
- ]
- }.freeze
- ].tap do |fields|
- next unless requires_webhook?
-
- fields.unshift(
- { type: :text, name: 'webhook', help: webhook_help, required: true }.freeze,
- { type: :text, name: 'username', placeholder: 'GitLab-integration' }.freeze
- )
- end.freeze
+ self.class.fields + build_event_channels
end
def execute(data)
@@ -154,6 +113,15 @@ module Integrations
supported_events.map { |event| event_channel_name(event) }
end
+ override :api_field_names
+ def api_field_names
+ if mask_configurable_channels?
+ super - event_channel_names
+ else
+ super
+ end
+ end
+
def form_fields
super.reject { |field| field[:name].end_with?('channel') }
end
@@ -166,6 +134,10 @@ module Integrations
raise NotImplementedError
end
+ def help
+ raise NotImplementedError
+ end
+
# With some integrations the webhook is already tied to a specific channel,
# for others the channels are configurable for each event.
def configurable_channels?
@@ -181,7 +153,7 @@ module Integrations
self.public_send(field_name) # rubocop:disable GitlabSecurity/PublicSend
end
- def requires_webhook?
+ def self.requires_webhook?
true
end
@@ -193,11 +165,32 @@ module Integrations
false
end
+ override :sections
+ def sections
+ [
+ {
+ type: SECTION_TYPE_CONNECTION,
+ title: s_('Integrations|Connection details'),
+ description: help
+ },
+ {
+ type: SECTION_TYPE_TRIGGER,
+ title: s_('Integrations|Trigger'),
+ description: s_('Integrations|An event will be triggered when one of the following items happen.')
+ },
+ {
+ type: SECTION_TYPE_CONFIGURATION,
+ title: s_('Integrations|Notification settings'),
+ description: s_('Integrations|Configure the scope of notifications.')
+ }
+ ]
+ end
+
private
def should_execute?(object_kind)
supported_events.include?(object_kind) &&
- (!requires_webhook? || webhook.present?)
+ (!self.class.requires_webhook? || webhook.present?)
end
def log_usage(_, _)
@@ -264,7 +257,7 @@ module Integrations
def build_event_channels
event_channel_names.map do |channel_field|
- { type: :text, name: channel_field, placeholder: default_channel_placeholder }
+ Field.new(name: channel_field, type: :text, placeholder: default_channel_placeholder, integration_class: self)
end
end
diff --git a/app/models/integrations/base_issue_tracker.rb b/app/models/integrations/base_issue_tracker.rb
index 7a54d354007..b59aee6743d 100644
--- a/app/models/integrations/base_issue_tracker.rb
+++ b/app/models/integrations/base_issue_tracker.rb
@@ -88,7 +88,7 @@ module Integrations
end
def self.supported_events
- %w(push)
+ %w[push]
end
def execute(data)
diff --git a/app/models/integrations/base_monitoring.rb b/app/models/integrations/base_monitoring.rb
index b0bebb5a859..12ea57f59a3 100644
--- a/app/models/integrations/base_monitoring.rb
+++ b/app/models/integrations/base_monitoring.rb
@@ -9,7 +9,7 @@ module Integrations
attribute :category, default: 'monitoring'
def self.supported_events
- %w()
+ %w[]
end
def can_query?
diff --git a/app/models/integrations/base_slack_notification.rb b/app/models/integrations/base_slack_notification.rb
index 29a20419809..65aec8b278f 100644
--- a/app/models/integrations/base_slack_notification.rb
+++ b/app/models/integrations/base_slack_notification.rb
@@ -25,20 +25,24 @@ module Integrations
override :supported_events
def supported_events
- additional = %w[alert]
-
- if group_level? && Feature.enabled?(:group_mentions, group)
- additional += %w[group_mention group_confidential_mention]
- end
+ additional = group_level? ? %w[group_mention group_confidential_mention] : []
(super + additional).freeze
end
+ def self.supported_events
+ super + %w[alert]
+ end
+
override :configurable_channels?
def configurable_channels?
true
end
+ def help
+ # noop
+ end
+
private
override :log_usage
diff --git a/app/models/integrations/base_slash_commands.rb b/app/models/integrations/base_slash_commands.rb
index 7662da933ba..58821e5fb4e 100644
--- a/app/models/integrations/base_slash_commands.rb
+++ b/app/models/integrations/base_slash_commands.rb
@@ -13,7 +13,7 @@ module Integrations
end
def self.supported_events
- %w()
+ %w[]
end
def testable?
diff --git a/app/models/integrations/base_third_party_wiki.rb b/app/models/integrations/base_third_party_wiki.rb
index 8df172e9a53..dee3706c518 100644
--- a/app/models/integrations/base_third_party_wiki.rb
+++ b/app/models/integrations/base_third_party_wiki.rb
@@ -9,7 +9,7 @@ module Integrations
after_commit :cache_project_has_integration
def self.supported_events
- %w()
+ %w[]
end
private
diff --git a/app/models/integrations/buildkite.rb b/app/models/integrations/buildkite.rb
index 6cd36e545a5..82a5142e8c2 100644
--- a/app/models/integrations/buildkite.rb
+++ b/app/models/integrations/buildkite.rb
@@ -29,7 +29,7 @@ module Integrations
validates :token, presence: true, if: :activated?
def self.supported_events
- %w(push merge_request tag_push)
+ %w[push merge_request tag_push]
end
# This is a stub method to work with deprecated API response
diff --git a/app/models/integrations/campfire.rb b/app/models/integrations/campfire.rb
index 007578e5830..8b5797a9d24 100644
--- a/app/models/integrations/campfire.rb
+++ b/app/models/integrations/campfire.rb
@@ -2,7 +2,7 @@
module Integrations
class Campfire < Integration
- SUBDOMAIN_REGEXP = %r{\A[a-z](?:[a-z0-9-]*[a-z0-9])?\z}i.freeze
+ SUBDOMAIN_REGEXP = %r{\A[a-z](?:[a-z0-9-]*[a-z0-9])?\z}i
validates :token, presence: true, if: :activated?
validates :room,
@@ -26,12 +26,9 @@ module Integrations
placeholder: '',
exposes_secrets: true,
help: -> do
- ERB::Util.html_escape(
+ format(ERB::Util.html_escape(
s_('CampfireService|The %{code_open}.campfirenow.com%{code_close} subdomain.')
- ) % {
- code_open: '<code>'.html_safe,
- code_close: '</code>'.html_safe
- }
+ ), code_open: '<code>'.html_safe, code_close: '</code>'.html_safe)
end
field :room,
@@ -48,13 +45,16 @@ module Integrations
end
def help
- docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('api/services', anchor: 'campfire'), target: '_blank', rel: 'noopener noreferrer'
-
- ERB::Util.html_escape(
+ docs_link = ActionController::Base.helpers.link_to(
+ _('Learn more.'),
+ Rails.application.routes.url_helpers.help_page_url('api/integrations', anchor: 'campfire'),
+ target: '_blank',
+ rel: 'noopener noreferrer'
+ )
+
+ format(ERB::Util.html_escape(
s_('CampfireService|Send notifications about push events to Campfire chat rooms. %{docs_link}')
- ) % {
- docs_link: docs_link.html_safe
- }
+ ), docs_link: docs_link.html_safe)
end
def self.to_param
@@ -62,14 +62,14 @@ module Integrations
end
def self.supported_events
- %w(push)
+ %w[push]
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
message = create_message(data)
- speak(self.room, message, auth)
+ speak(room, message, auth)
end
private
@@ -96,7 +96,7 @@ module Integrations
room = rooms(auth).find { |r| r["name"] == room_name }
return unless room
- path = "/room/#{room["id"]}/speak.json"
+ path = "/room/#{room['id']}/speak.json"
body = {
body: {
message: {
diff --git a/app/models/integrations/chat_message/base_message.rb b/app/models/integrations/chat_message/base_message.rb
index 501b214a769..600f07b97f1 100644
--- a/app/models/integrations/chat_message/base_message.rb
+++ b/app/models/integrations/chat_message/base_message.rb
@@ -3,7 +3,7 @@
module Integrations
module ChatMessage
class BaseMessage
- RELATIVE_LINK_REGEX = %r{!\[[^\]]*\]\((/uploads/[^\)]*)\)}.freeze
+ RELATIVE_LINK_REGEX = %r{!\[[^\]]*\]\((/uploads/[^\)]*)\)}
attr_reader :markdown
attr_reader :user_full_name
diff --git a/app/models/integrations/chat_message/deployment_message.rb b/app/models/integrations/chat_message/deployment_message.rb
index b28edeecb4d..0367459dfcb 100644
--- a/app/models/integrations/chat_message/deployment_message.rb
+++ b/app/models/integrations/chat_message/deployment_message.rb
@@ -26,8 +26,10 @@ module Integrations
end
def attachments
+ return description_message if markdown
+
[{
- text: "#{project_link} with job #{deployment_link} by #{user_link}\n#{commit_link}: #{strip_markup(commit_title)}",
+ text: format(description_message),
color: color
}]
end
@@ -82,6 +84,10 @@ module Integrations
def running?
status == 'running'
end
+
+ def description_message
+ "#{project_link} with job #{deployment_link} by #{user_link}\n#{commit_link}: #{strip_markup(commit_title)}"
+ end
end
end
end
diff --git a/app/models/integrations/confluence.rb b/app/models/integrations/confluence.rb
index 31e9a171d1b..eda8c37fc72 100644
--- a/app/models/integrations/confluence.rb
+++ b/app/models/integrations/confluence.rb
@@ -2,9 +2,9 @@
module Integrations
class Confluence < BaseThirdPartyWiki
- VALID_SCHEME_MATCH = %r{\Ahttps?\Z}.freeze
- VALID_HOST_MATCH = %r{\A.+\.atlassian\.net\Z}.freeze
- VALID_PATH_MATCH = %r{\A/wiki(/|\Z)}.freeze
+ VALID_SCHEME_MATCH = %r{\Ahttps?\Z}
+ VALID_HOST_MATCH = %r{\A.+\.atlassian\.net\Z}
+ VALID_PATH_MATCH = %r{\A/wiki(/|\Z)}
validates :confluence_url, presence: true, if: :activated?
validate :validate_confluence_url_is_cloud, if: :activated?
@@ -14,6 +14,10 @@ module Integrations
placeholder: 'https://example.atlassian.net/wiki',
required: true
+ def avatar_url
+ ActionController::Base.helpers.image_path('confluence.svg')
+ end
+
def self.to_param
'confluence'
end
diff --git a/app/models/integrations/datadog.rb b/app/models/integrations/datadog.rb
index 1a56763fe57..b1f1361afcd 100644
--- a/app/models/integrations/datadog.rb
+++ b/app/models/integrations/datadog.rb
@@ -12,7 +12,7 @@ module Integrations
pipeline build archive_trace
].freeze
- TAG_KEY_VALUE_RE = %r{\A [\w-]+ : .*\S.* \z}x.freeze
+ TAG_KEY_VALUE_RE = %r{\A [\w-]+ : .*\S.* \z}x
field :datadog_site,
exposes_secrets: true,
@@ -40,7 +40,7 @@ module Integrations
ERB::Util.html_escape(
s_('DatadogIntegration|%{linkOpen}API key%{linkClose} used for authentication with Datadog.')
) % {
- linkOpen: %{<a href="#{URL_API_KEYS_DOCS}" target="_blank" rel="noopener noreferrer">}.html_safe,
+ linkOpen: %(<a href="#{URL_API_KEYS_DOCS}" target="_blank" rel="noopener noreferrer">).html_safe,
linkClose: '</a>'.html_safe
}
end,
diff --git a/app/models/integrations/discord.rb b/app/models/integrations/discord.rb
index 7cae3ca20f9..815e3669d78 100644
--- a/app/models/integrations/discord.rb
+++ b/app/models/integrations/discord.rb
@@ -4,9 +4,7 @@ require "discordrb/webhooks"
module Integrations
class Discord < BaseChatNotification
- ATTACHMENT_REGEX = /: (?<entry>.*?)\n - (?<name>.*)\n*/.freeze
-
- undef :notify_only_broken_pipelines
+ ATTACHMENT_REGEX = /: (?<entry>.*?)\n - (?<name>.*)\n*/
field :webhook,
section: SECTION_TYPE_CONNECTION,
@@ -35,10 +33,6 @@ module Integrations
"discord"
end
- def fields
- self.class.fields + build_event_channels
- end
-
def help
docs_link = ActionController::Base.helpers.link_to _('How do I set up this service?'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/discord_notifications'), target: '_blank', rel: 'noopener noreferrer'
s_('Send notifications about project events to a Discord channel. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
@@ -52,26 +46,6 @@ module Integrations
%w[push issue confidential_issue merge_request note confidential_note tag_push pipeline wiki_page]
end
- def sections
- [
- {
- type: SECTION_TYPE_CONNECTION,
- title: s_('Integrations|Connection details'),
- description: help
- },
- {
- type: SECTION_TYPE_TRIGGER,
- title: s_('Integrations|Trigger'),
- description: s_('Integrations|An event will be triggered when one of the following items happen.')
- },
- {
- type: SECTION_TYPE_CONFIGURATION,
- title: s_('Integrations|Notification settings'),
- description: s_('Integrations|Configure the scope of notifications.')
- }
- ]
- end
-
def configurable_channels?
true
end
diff --git a/app/models/integrations/drone_ci.rb b/app/models/integrations/drone_ci.rb
index ac464c020dd..f6a12c4bb1a 100644
--- a/app/models/integrations/drone_ci.rb
+++ b/app/models/integrations/drone_ci.rb
@@ -43,7 +43,7 @@ module Integrations
end
def self.supported_events
- %w(push merge_request tag_push)
+ %w[push merge_request tag_push]
end
def commit_status_path(sha, ref)
diff --git a/app/models/integrations/emails_on_push.rb b/app/models/integrations/emails_on_push.rb
index eb893ae45d0..144d1a07b04 100644
--- a/app/models/integrations/emails_on_push.rb
+++ b/app/models/integrations/emails_on_push.rb
@@ -52,7 +52,7 @@ module Integrations
end
def self.supported_events
- %w(push tag_push)
+ %w[push tag_push]
end
def initialize_properties
diff --git a/app/models/integrations/external_wiki.rb b/app/models/integrations/external_wiki.rb
index 75fe6b6f164..acacab2528e 100644
--- a/app/models/integrations/external_wiki.rb
+++ b/app/models/integrations/external_wiki.rb
@@ -47,7 +47,7 @@ module Integrations
end
def self.supported_events
- %w()
+ %w[]
end
end
end
diff --git a/app/models/integrations/gitlab_slack_application.rb b/app/models/integrations/gitlab_slack_application.rb
index b0f54f39e8c..2d520eaf7e7 100644
--- a/app/models/integrations/gitlab_slack_application.rb
+++ b/app/models/integrations/gitlab_slack_application.rb
@@ -20,6 +20,8 @@ module Integrations
has_one :slack_integration, foreign_key: :integration_id, inverse_of: :integration
delegate :bot_access_token, :bot_user_id, to: :slack_integration, allow_nil: true
+ include SlackMattermostFields
+
def update_active_status
update(active: !!slack_integration)
end
@@ -66,18 +68,7 @@ module Integrations
def sections
return [] unless editable?
- [
- {
- type: SECTION_TYPE_TRIGGER,
- title: s_('Integrations|Trigger'),
- description: s_('Integrations|An event will be triggered when one of the following items happen.')
- },
- {
- type: SECTION_TYPE_CONFIGURATION,
- title: s_('Integrations|Notification settings'),
- description: s_('Integrations|Configure the scope of notifications.')
- }
- ]
+ super.drop(1)
end
override :configurable_events
@@ -88,7 +79,7 @@ module Integrations
end
override :requires_webhook?
- def requires_webhook?
+ def self.requires_webhook?
false
end
diff --git a/app/models/integrations/hangouts_chat.rb b/app/models/integrations/hangouts_chat.rb
index 037c689c75e..680752c3d56 100644
--- a/app/models/integrations/hangouts_chat.rb
+++ b/app/models/integrations/hangouts_chat.rb
@@ -2,8 +2,6 @@
module Integrations
class HangoutsChat < BaseChatNotification
- undef :notify_only_broken_pipelines
-
field :webhook,
section: SECTION_TYPE_CONNECTION,
help: 'https://chat.googleapis.com/v1/spaces…',
@@ -36,10 +34,6 @@ module Integrations
s_('Before enabling this integration, create a webhook for the room in Google Chat where you want to receive notifications from this project. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
- def fields
- self.class.fields + build_event_channels
- end
-
def default_channel_placeholder
end
diff --git a/app/models/integrations/jenkins.rb b/app/models/integrations/jenkins.rb
index 7769ea7d2dd..0683c8408bc 100644
--- a/app/models/integrations/jenkins.rb
+++ b/app/models/integrations/jenkins.rb
@@ -66,7 +66,7 @@ module Integrations
end
def self.supported_events
- %w(push merge_request tag_push)
+ %w[push merge_request tag_push]
end
def title
diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb
index faf0a378a17..d8d1f860e9a 100644
--- a/app/models/integrations/jira.rb
+++ b/app/models/integrations/jira.rb
@@ -126,7 +126,7 @@ module Integrations
# When these are false GitLab does not create cross reference
# comments on Jira except when an issue gets transitioned.
def self.supported_events
- %w(commit merge_request)
+ %w[commit merge_request]
end
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
diff --git a/app/models/integrations/mattermost.rb b/app/models/integrations/mattermost.rb
index e3c5c22ad3a..7e391b11d82 100644
--- a/app/models/integrations/mattermost.rb
+++ b/app/models/integrations/mattermost.rb
@@ -3,6 +3,7 @@
module Integrations
class Mattermost < BaseChatNotification
include SlackMattermostNotifier
+ include SlackMattermostFields
def title
_('Mattermost notifications')
@@ -25,7 +26,7 @@ module Integrations
'my-channel'
end
- def webhook_help
+ def self.webhook_help
'http://mattermost.example.com/hooks/'
end
diff --git a/app/models/integrations/microsoft_teams.rb b/app/models/integrations/microsoft_teams.rb
index 25308948d51..208172d6303 100644
--- a/app/models/integrations/microsoft_teams.rb
+++ b/app/models/integrations/microsoft_teams.rb
@@ -2,8 +2,6 @@
module Integrations
class MicrosoftTeams < BaseChatNotification
- undef :notify_only_broken_pipelines
-
field :webhook,
section: SECTION_TYPE_CONNECTION,
help: 'https://outlook.office.com/webhook/…',
@@ -44,30 +42,6 @@ module Integrations
pipeline wiki_page]
end
- def fields
- self.class.fields + build_event_channels
- end
-
- def sections
- [
- {
- type: SECTION_TYPE_CONNECTION,
- title: s_('Integrations|Connection details'),
- description: help
- },
- {
- type: SECTION_TYPE_TRIGGER,
- title: s_('Integrations|Trigger'),
- description: s_('Integrations|An event will be triggered when one of the following items happen.')
- },
- {
- type: SECTION_TYPE_CONFIGURATION,
- title: s_('Integrations|Notification settings'),
- description: s_('Integrations|Configure the scope of notifications.')
- }
- ]
- end
-
private
def notify(message, opts)
diff --git a/app/models/integrations/packagist.rb b/app/models/integrations/packagist.rb
index c9c08ec9771..c0acb6c87b4 100644
--- a/app/models/integrations/packagist.rb
+++ b/app/models/integrations/packagist.rb
@@ -42,7 +42,7 @@ module Integrations
end
def self.supported_events
- %w(push merge_request tag_push)
+ %w[push merge_request tag_push]
end
def execute(data)
diff --git a/app/models/integrations/pivotaltracker.rb b/app/models/integrations/pivotaltracker.rb
index 0d9a3f05a86..f42a872c49e 100644
--- a/app/models/integrations/pivotaltracker.rb
+++ b/app/models/integrations/pivotaltracker.rb
@@ -38,7 +38,7 @@ module Integrations
end
def self.supported_events
- %w(push)
+ %w[push]
end
def execute(data)
diff --git a/app/models/integrations/prometheus.rb b/app/models/integrations/prometheus.rb
index 736318ed707..8474a5b7adf 100644
--- a/app/models/integrations/prometheus.rb
+++ b/app/models/integrations/prometheus.rb
@@ -41,6 +41,7 @@ module Integrations
before_save :synchronize_service_state
after_save :clear_reactive_cache!
+ after_commit :sync_http_integration!
after_commit :track_events
@@ -180,5 +181,16 @@ module Integrations
nil
end
strong_memoize_attr :iap_client
+
+ # Remove in next required stop after %16.4
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/338838
+ def sync_http_integration!
+ return unless manual_configuration_changed?
+
+ project.alert_management_http_integrations
+ .for_endpoint_identifier('legacy-prometheus')
+ .take
+ &.update_columns(active: manual_configuration)
+ end
end
end
diff --git a/app/models/integrations/pumble.rb b/app/models/integrations/pumble.rb
index 8f0dddcc5c5..09e011023ed 100644
--- a/app/models/integrations/pumble.rb
+++ b/app/models/integrations/pumble.rb
@@ -2,8 +2,6 @@
module Integrations
class Pumble < BaseChatNotification
- undef :notify_only_broken_pipelines
-
field :webhook,
section: SECTION_TYPE_CONNECTION,
help: 'https://api.pumble.com/workspaces/x/...',
@@ -52,10 +50,6 @@ module Integrations
pipeline wiki_page]
end
- def fields
- self.class.fields + build_event_channels
- end
-
private
def notify(message, opts)
diff --git a/app/models/integrations/pushover.rb b/app/models/integrations/pushover.rb
index 006b731c6c2..e97c7e5e738 100644
--- a/app/models/integrations/pushover.rb
+++ b/app/models/integrations/pushover.rb
@@ -47,19 +47,19 @@ module Integrations
[
['Device default sound', nil],
['Pushover (default)', 'pushover'],
- %w(Bike bike),
- %w(Bugle bugle),
+ %w[Bike bike],
+ %w[Bugle bugle],
['Cash Register', 'cashregister'],
- %w(Classical classical),
- %w(Cosmic cosmic),
- %w(Falling falling),
- %w(Gamelan gamelan),
- %w(Incoming incoming),
- %w(Intermission intermission),
- %w(Magic magic),
- %w(Mechanical mechanical),
+ %w[Classical classical],
+ %w[Cosmic cosmic],
+ %w[Falling falling],
+ %w[Gamelan gamelan],
+ %w[Incoming incoming],
+ %w[Intermission intermission],
+ %w[Magic magic],
+ %w[Mechanical mechanical],
['Piano Bar', 'pianobar'],
- %w(Siren siren),
+ %w[Siren siren],
['Space Alarm', 'spacealarm'],
['Tug Boat', 'tugboat'],
['Alien Alarm (long)', 'alien'],
@@ -84,7 +84,7 @@ module Integrations
end
def self.supported_events
- %w(push)
+ %w[push]
end
def execute(data)
diff --git a/app/models/integrations/shimo.rb b/app/models/integrations/shimo.rb
index f5b6595fff2..227fdca5c91 100644
--- a/app/models/integrations/shimo.rb
+++ b/app/models/integrations/shimo.rb
@@ -8,6 +8,10 @@ module Integrations
title: -> { s_('Shimo|Shimo Workspace URL') },
required: true
+ def avatar_url
+ ActionController::Base.helpers.image_path('logos/shimo.svg')
+ end
+
def render?
valid? && activated?
end
diff --git a/app/models/integrations/slack.rb b/app/models/integrations/slack.rb
index 07d2d802915..f70376e2f0d 100644
--- a/app/models/integrations/slack.rb
+++ b/app/models/integrations/slack.rb
@@ -3,6 +3,7 @@
module Integrations
class Slack < BaseSlackNotification
include SlackMattermostNotifier
+ include SlackMattermostFields
def title
'Slack notifications'
@@ -16,8 +17,7 @@ module Integrations
'slack'
end
- override :webhook_help
- def webhook_help
+ def self.webhook_help
'https://hooks.slack.com/services/…'
end
diff --git a/app/models/integrations/teamcity.rb b/app/models/integrations/teamcity.rb
index c74e0aab030..575c3b8a334 100644
--- a/app/models/integrations/teamcity.rb
+++ b/app/models/integrations/teamcity.rb
@@ -6,7 +6,7 @@ module Integrations
include ReactivelyCached
prepend EnableSslVerification
- TEAMCITY_SAAS_HOSTNAME = /\A[^\.]+\.teamcity\.com\z/i.freeze
+ TEAMCITY_SAAS_HOSTNAME = /\A[^\.]+\.teamcity\.com\z/i
field :teamcity_url,
title: -> { s_('ProjectService|TeamCity server URL') },
@@ -43,7 +43,7 @@ module Integrations
end
def supported_events
- %w(push merge_request)
+ %w[push merge_request]
end
end
diff --git a/app/models/integrations/telegram.rb b/app/models/integrations/telegram.rb
index 9af12c712c6..7c196720386 100644
--- a/app/models/integrations/telegram.rb
+++ b/app/models/integrations/telegram.rb
@@ -21,6 +21,11 @@ module Integrations
placeholder: '@channelusername',
required: true
+ field :notify_only_broken_pipelines,
+ type: :checkbox,
+ section: SECTION_TYPE_CONFIGURATION,
+ help: 'If selected, successful pipelines do not trigger a notification event.'
+
with_options if: :activated? do
validates :token, :room, presence: true
end
@@ -51,34 +56,10 @@ module Integrations
)
end
- def fields
- self.class.fields + build_event_channels
- end
-
def self.supported_events
super - ['deployment']
end
- def sections
- [
- {
- type: SECTION_TYPE_CONNECTION,
- title: s_('Integrations|Connection details'),
- description: help
- },
- {
- type: SECTION_TYPE_TRIGGER,
- title: s_('Integrations|Trigger'),
- description: s_('Integrations|An event will be triggered when one of the following items happen.')
- },
- {
- type: SECTION_TYPE_CONFIGURATION,
- title: s_('Integrations|Notification settings'),
- description: s_('Integrations|Configure the scope of notifications.')
- }
- ]
- end
-
private
def set_webhook
diff --git a/app/models/integrations/unify_circuit.rb b/app/models/integrations/unify_circuit.rb
index 6de693b5278..3b4bcfa28d3 100644
--- a/app/models/integrations/unify_circuit.rb
+++ b/app/models/integrations/unify_circuit.rb
@@ -2,8 +2,6 @@
module Integrations
class UnifyCircuit < BaseChatNotification
- undef :notify_only_broken_pipelines
-
field :webhook,
section: SECTION_TYPE_CONNECTION,
help: 'https://yourcircuit.com/rest/v2/webhooks/incoming/…',
@@ -31,10 +29,6 @@ module Integrations
'unify_circuit'
end
- def fields
- self.class.fields + build_event_channels
- end
-
def help
docs_link = ActionController::Base.helpers.link_to _('How do I set up this service?'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/unify_circuit'), target: '_blank', rel: 'noopener noreferrer'
s_('Integrations|Send notifications about project events to a Unify Circuit conversation. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
diff --git a/app/models/integrations/webex_teams.rb b/app/models/integrations/webex_teams.rb
index 21c65cc2b32..3ef8ab39352 100644
--- a/app/models/integrations/webex_teams.rb
+++ b/app/models/integrations/webex_teams.rb
@@ -2,8 +2,6 @@
module Integrations
class WebexTeams < BaseChatNotification
- undef :notify_only_broken_pipelines
-
field :webhook,
section: SECTION_TYPE_CONNECTION,
help: 'https://api.ciscospark.com/v1/webhooks/incoming/...',
@@ -31,10 +29,6 @@ module Integrations
'webex_teams'
end
- def fields
- self.class.fields + build_event_channels
- end
-
def help
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/webex_teams'), target: '_blank', rel: 'noopener noreferrer'
s_("WebexTeamsService|Send notifications about project events to a Webex Teams conversation. %{docs_link}") % { docs_link: docs_link.html_safe }
diff --git a/app/models/integrations/zentao.rb b/app/models/integrations/zentao.rb
index fd2c741bd6b..58ec4abf30c 100644
--- a/app/models/integrations/zentao.rb
+++ b/app/models/integrations/zentao.rb
@@ -34,6 +34,10 @@ module Integrations
validates :api_token, presence: true, if: :activated?
validates :zentao_product_xid, presence: true, if: :activated?
+ def avatar_url
+ ActionController::Base.helpers.image_path('logos/zentao.svg')
+ end
+
def self.issues_license_available?(project)
project&.licensed_feature_available?(:zentao_issues_integration)
end
@@ -82,7 +86,7 @@ module Integrations
end
def self.supported_events
- %w()
+ %w[]
end
private
diff --git a/app/models/issuable_severity.rb b/app/models/issuable_severity.rb
index cd7e5fafb60..08984bbb723 100644
--- a/app/models/issuable_severity.rb
+++ b/app/models/issuable_severity.rb
@@ -11,11 +11,11 @@ class IssuableSeverity < ApplicationRecord
}.freeze
SEVERITY_QUICK_ACTION_PARAMS = {
- unknown: %w(Unknown 0),
- low: %w(Low S4 4),
- medium: %w(Medium S3 3),
- high: %w(High S2 2),
- critical: %w(Critical S1 1)
+ unknown: %w[Unknown 0],
+ low: %w[Low S4 4],
+ medium: %w[Medium S3 3],
+ high: %w[High S2 2],
+ critical: %w[Critical S1 1]
}.freeze
belongs_to :issue
diff --git a/app/models/issue.rb b/app/models/issue.rb
index d227448961a..58383a6a329 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -14,7 +14,6 @@ class Issue < ApplicationRecord
include TimeTrackable
include ThrottledTouch
include LabelEventable
- include IgnorableColumns
include MilestoneEventable
include WhereComposite
include StateEventable
@@ -48,16 +47,14 @@ class Issue < ApplicationRecord
#
# This should be kept consistent with the enums used for the GraphQL issue list query in
# https://gitlab.com/gitlab-org/gitlab/-/blob/1379c2d7bffe2a8d809f23ac5ef9b4114f789c07/app/assets/javascripts/issues/list/constants.js#L154-158
- TYPES_FOR_LIST = %w(issue incident test_case task objective key_result).freeze
+ TYPES_FOR_LIST = %w[issue incident test_case task objective key_result].freeze
# Types of issues that should be displayed on issue board lists
- TYPES_FOR_BOARD_LIST = %w(issue incident).freeze
+ TYPES_FOR_BOARD_LIST = %w[issue incident].freeze
# This default came from the enum `issue_type` column. Defined as default in the DB
DEFAULT_ISSUE_TYPE = :issue
- ignore_column :issue_type, remove_with: '16.4', remove_after: '2023-08-22'
-
belongs_to :project
belongs_to :namespace, inverse_of: :issues
@@ -112,7 +109,6 @@ class Issue < ApplicationRecord
has_one :sentry_issue
has_one :alert_management_alert, class_name: 'AlertManagement::Alert'
has_one :incident_management_issuable_escalation_status, class_name: 'IncidentManagement::IssuableEscalationStatus'
- has_and_belongs_to_many :self_managed_prometheus_alert_events, join_table: :issues_self_managed_prometheus_alert_events # rubocop: disable Rails/HasAndBelongsToMany
has_and_belongs_to_many :prometheus_alert_events, join_table: :issues_prometheus_alert_events # rubocop: disable Rails/HasAndBelongsToMany
has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :issue, validate: false
has_many :prometheus_alerts, through: :prometheus_alert_events
@@ -190,7 +186,6 @@ class Issue < ApplicationRecord
scope :preload_awardable, -> { preload(:award_emoji) }
scope :with_alert_management_alerts, -> { joins(:alert_management_alert) }
scope :with_prometheus_alert_events, -> { joins(:issues_prometheus_alert_events) }
- scope :with_self_managed_prometheus_alert_events, -> { joins(:issues_self_managed_prometheus_alert_events) }
scope :with_api_entity_associations, -> {
preload(:work_item_type, :timelogs, :closed_by, :assignees, :author, :labels, :issuable_severity,
namespace: [{ parent: :route }, :route], milestone: { project: [:route, { namespace: :route }] },
@@ -223,8 +218,11 @@ class Issue < ApplicationRecord
scope :counts_by_state, -> { reorder(nil).group(:state_id).count }
- scope :service_desk, -> { where(author: ::User.support_bot) }
- scope :inc_relations_for_view, -> { includes(author: :status, assignees: :status) }
+ scope :service_desk, -> { where(author: ::Users::Internal.support_bot) }
+ scope :inc_relations_for_view, -> do
+ includes(author: :status, assignees: :status)
+ .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/422155')
+ end
# An issue can be uniquely identified by project_id and iid
# Takes one or more sets of composite IDs, expressed as hash-like records of
@@ -546,18 +544,14 @@ class Issue < ApplicationRecord
end
def related_issues(current_user, preload: nil)
- related_issues = self.class
- .select(['issues.*', 'issue_links.id AS issue_link_id',
- 'issue_links.link_type as issue_link_type_value',
- 'issue_links.target_id as issue_link_source_id',
- 'issue_links.created_at as issue_link_created_at',
- 'issue_links.updated_at as issue_link_updated_at'])
- .joins("INNER JOIN issue_links ON
- (issue_links.source_id = issues.id AND issue_links.target_id = #{id})
- OR
- (issue_links.target_id = issues.id AND issue_links.source_id = #{id})")
- .preload(preload)
- .reorder('issue_link_id')
+ related_issues =
+ linked_issues_select
+ .joins("INNER JOIN issue_links ON
+ (issue_links.source_id = issues.id AND issue_links.target_id = #{id})
+ OR
+ (issue_links.target_id = issues.id AND issue_links.source_id = #{id})")
+ .preload(preload)
+ .reorder('issue_link_id')
related_issues = yield related_issues if block_given?
@@ -607,7 +601,7 @@ class Issue < ApplicationRecord
end
end
- def etag_caching_enabled?
+ def real_time_notes_enabled?
true
end
@@ -642,7 +636,7 @@ class Issue < ApplicationRecord
end
def from_service_desk?
- author.id == User.support_bot.id
+ author.id == Users::Internal.support_bot.id
end
def issue_link_type
@@ -716,8 +710,8 @@ class Issue < ApplicationRecord
end
def expire_etag_cache
- # TODO: Fix this for the case when issues is created at group level
- # TODO: https://gitlab.com/gitlab-org/gitlab/-/work_items/395814
+ # We don't expire the cache for issues that don't have a project, since they are created at the group level
+ # and they are only displayed in the new work item view that uses GraphQL subscriptions for real-time updates
return unless project
key = Gitlab::Routing.url_helpers.realtime_changes_project_issue_path(project, self)
@@ -789,7 +783,7 @@ class Issue < ApplicationRecord
# TODO: https://gitlab.com/gitlab-org/gitlab/-/work_items/393126
return unless project
- Issues::SearchData.upsert({ namespace_id: namespace_id, project_id: project_id, issue_id: id, search_vector: search_vector }, unique_by: %i(project_id issue_id))
+ Issues::SearchData.upsert({ namespace_id: namespace_id, project_id: project_id, issue_id: id, search_vector: search_vector }, unique_by: %i[project_id issue_id])
end
def ensure_metrics!
@@ -833,6 +827,14 @@ class Issue < ApplicationRecord
errors.add(:work_item_type_id, format(_('can not be changed to %{new_type}'), new_type: work_item_type&.name))
end
+
+ def linked_issues_select
+ self.class.select(['issues.*', 'issue_links.id AS issue_link_id',
+ 'issue_links.link_type as issue_link_type_value',
+ 'issue_links.target_id as issue_link_source_id',
+ 'issue_links.created_at as issue_link_created_at',
+ 'issue_links.updated_at as issue_link_updated_at'])
+ end
end
Issue.prepend_mod_with('Issue')
diff --git a/app/models/label_link.rb b/app/models/label_link.rb
index d326b07ad31..0c2d205c641 100644
--- a/app/models/label_link.rb
+++ b/app/models/label_link.rb
@@ -24,3 +24,5 @@ class LabelLink < ApplicationRecord
relation
end
end
+
+LabelLink.prepend_mod_with('LabelLink')
diff --git a/app/models/lfs_download_object.rb b/app/models/lfs_download_object.rb
index 3df6742fbc9..046e47262dd 100644
--- a/app/models/lfs_download_object.rb
+++ b/app/models/lfs_download_object.rb
@@ -9,7 +9,7 @@ class LfsDownloadObject
validates :oid, format: { with: /\A\h{64}\z/ }
validates :size, numericality: { greater_than_or_equal_to: 0 }
- validates :link, public_url: { protocols: %w(http https) }
+ validates :link, public_url: { protocols: %w[http https] }
validate :headers_must_be_hash
def initialize(oid:, size:, link:, headers: {})
diff --git a/app/models/license_template.rb b/app/models/license_template.rb
index 548066107c1..bfe2a8d379e 100644
--- a/app/models/license_template.rb
+++ b/app/models/license_template.rb
@@ -5,12 +5,12 @@ class LicenseTemplate
%r{[\<\{\[]
(project|description|
one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
- [\>\}\]]}xi.freeze
- YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
+ [\>\}\]]}xi
+ YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i
FULLNAME_TEMPLATE_REGEX =
%r{[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
- [\>\}\]]}xi.freeze
+ [\>\}\]]}xi
attr_reader :key, :name, :project, :category, :nickname, :url, :meta
diff --git a/app/models/loose_foreign_keys/modification_tracker.rb b/app/models/loose_foreign_keys/modification_tracker.rb
index 72a596d2114..eec9b8ad285 100644
--- a/app/models/loose_foreign_keys/modification_tracker.rb
+++ b/app/models/loose_foreign_keys/modification_tracker.rb
@@ -2,10 +2,6 @@
module LooseForeignKeys
class ModificationTracker
- MAX_DELETES = 100_000
- MAX_UPDATES = 50_000
- MAX_RUNTIME = 30.seconds # must be less than the scheduling frequency of the LooseForeignKeys::CleanupWorker cron worker
-
delegate :monotonic_time, to: :'Gitlab::Metrics::System'
def initialize
@@ -22,6 +18,18 @@ module LooseForeignKeys
)
end
+ def max_runtime
+ 30.seconds
+ end
+
+ def max_deletes
+ 100_000
+ end
+
+ def max_updates
+ 50_000
+ end
+
def add_deletions(table, count)
@delete_count_by_table[table] += count
@deletes_counter.increment({ table: table }, count)
@@ -33,9 +41,9 @@ module LooseForeignKeys
end
def over_limit?
- @delete_count_by_table.values.sum >= MAX_DELETES ||
- @update_count_by_table.values.sum >= MAX_UPDATES ||
- monotonic_time - @start_time >= MAX_RUNTIME
+ @delete_count_by_table.values.sum >= max_deletes ||
+ @update_count_by_table.values.sum >= max_updates ||
+ monotonic_time - @start_time >= max_runtime
end
def stats
diff --git a/app/models/loose_foreign_keys/turbo_modification_tracker.rb b/app/models/loose_foreign_keys/turbo_modification_tracker.rb
new file mode 100644
index 00000000000..5229b17e971
--- /dev/null
+++ b/app/models/loose_foreign_keys/turbo_modification_tracker.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module LooseForeignKeys
+ # This is a modification tracker with the additional limits that can be enabled
+ # for some database via an OPS Feature Flag.
+
+ class TurboModificationTracker < ModificationTracker
+ extend ::Gitlab::Utils::Override
+
+ override :max_runtime
+ def max_runtime
+ 45.seconds
+ end
+
+ override :max_deletes
+ def max_deletes
+ 200_000
+ end
+
+ override :max_updates
+ def max_updates
+ 150_000
+ end
+ end
+end
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index ada89345a7f..52b9c3a80e3 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -5,11 +5,10 @@ class GroupMember < Member
include CreatedAtFilterable
SOURCE_TYPE = 'Namespace'
- SOURCE_TYPE_FORMAT = /\ANamespace\z/.freeze
+ SOURCE_TYPE_FORMAT = /\ANamespace\z/
belongs_to :group, foreign_key: 'source_id'
alias_attribute :namespace_id, :source_id
- delegate :update_two_factor_requirement, to: :user, allow_nil: true
# Make sure group member points only to group as it source
attribute :source_type, default: SOURCE_TYPE
@@ -26,6 +25,16 @@ class GroupMember < Member
attr_accessor :last_owner
+ def update_two_factor_requirement
+ return unless user
+
+ Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.temporary_ignore_tables_in_transaction(
+ %w[users user_details user_preferences], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424288'
+ ) do
+ user.update_two_factor_requirement
+ end
+ end
+
# For those who get to see a modal with a role dropdown, here are the options presented
def self.permissible_access_level_roles(_, _)
# This method is a stopgap in preparation for https://gitlab.com/gitlab-org/gitlab/-/issues/364087
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index e0fecf702de..d07e4f9e298 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -2,7 +2,7 @@
class ProjectMember < Member
SOURCE_TYPE = 'Project'
- SOURCE_TYPE_FORMAT = /\AProject\z/.freeze
+ SOURCE_TYPE_FORMAT = /\AProject\z/
belongs_to :project, foreign_key: 'source_id'
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 469dba42952..6a72ed6476e 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -66,7 +66,7 @@ class MergeRequest < ApplicationRecord
belongs_to :latest_merge_request_diff, class_name: 'MergeRequestDiff'
manual_inverse_association :latest_merge_request_diff, :merge_request
- # method overriden in EE
+ # method overridden in EE
def suggested_reviewer_users
User.none
end
@@ -162,7 +162,7 @@ class MergeRequest < ApplicationRecord
# Keep states definition to be evaluated before the state_machine block to
# avoid spec failures. If this gets evaluated after, the `merged` and `locked`
- # states (which are overriden) can be nil.
+ # states (which are overridden) can be nil.
#
def self.available_state_names
super + [:merged, :locked]
@@ -279,6 +279,12 @@ class MergeRequest < ApplicationRecord
def check_state?(merge_status)
[:unchecked, :cannot_be_merged_recheck, :checking, :cannot_be_merged_rechecking].include?(merge_status.to_sym)
end
+
+ # rubocop: disable Style/SymbolProc
+ before_transition { |merge_request| merge_request.enable_transitioning }
+
+ after_transition { |merge_request| merge_request.disable_transitioning }
+ # rubocop: enable Style/SymbolProc
end
# Returns current merge_status except it returns `cannot_be_merged_rechecking` as `checking`
@@ -292,10 +298,14 @@ class MergeRequest < ApplicationRecord
validates :target_project, presence: true
validates :target_branch, presence: true
validates :merge_user, presence: true, if: :auto_merge_enabled?, unless: :importing?
- validate :validate_branches, unless: [:allow_broken, :importing?, :closed_or_merged_without_fork?]
+ validate :validate_branches, unless: [
+ :allow_broken,
+ :importing_or_transitioning?,
+ :closed_or_merged_without_fork?
+ ]
validate :validate_fork, unless: :closed_or_merged_without_fork?
- validate :validate_target_project, on: :create, unless: :importing?
- validate :validate_reviewer_size_length, unless: :importing?
+ validate :validate_target_project, on: :create, unless: :importing_or_transitioning?
+ validate :validate_reviewer_size_length, unless: :importing_or_transitioning?
scope :by_source_or_target_branch, ->(branch_name) do
where("source_branch = :branch OR target_branch = :branch", branch: branch_name)
@@ -371,6 +381,7 @@ class MergeRequest < ApplicationRecord
scope :with_csv_entity_associations, -> { preload(:assignees, :approved_by_users, :author, :milestone, metrics: [:merged_by]) }
scope :with_jira_integration_associations, -> { preload_routables.preload(:metrics, :assignees, :author) }
+ scope :recently_unprepared, -> { where(prepared_at: nil).where(created_at: 2.hours.ago..).order(:created_at, :id) } # id is the tie-breaker
scope :by_target_branch_wildcard, ->(wildcard_branch_name) do
where("target_branch LIKE ?", ApplicationRecord.sanitize_sql_like(wildcard_branch_name).tr('*', '%'))
@@ -550,13 +561,9 @@ class MergeRequest < ApplicationRecord
end
def merge_pipeline
- return unless merged?
-
- # When the merge_method is :merge there will be a merge_commit_sha, however
- # when it is fast-forward there is no merge commit, so we must fall back to
- # either the squash commit (if the MR was squashed) or the diff head commit.
- sha = merge_commit_sha || squash_commit_sha || diff_head_sha
- target_project.latest_pipeline(target_branch, sha)
+ if sha = merged_commit_sha
+ target_project.latest_pipeline(target_branch, sha)
+ end
end
def head_pipeline_active?
@@ -632,7 +639,7 @@ class MergeRequest < ApplicationRecord
end
end
- DRAFT_REGEX = /\A*#{Gitlab::Regex.merge_request_draft}+\s*/i.freeze
+ DRAFT_REGEX = /\A*#{Gitlab::Regex.merge_request_draft}+\s*/i
def self.draft?(title)
!!(title =~ DRAFT_REGEX)
@@ -734,6 +741,12 @@ class MergeRequest < ApplicationRecord
true
end
+ def supports_lock_on_merge?
+ return false unless merged?
+
+ project.supports_lock_on_merge?
+ end
+
# Calls `MergeWorker` to proceed with the merge process and
# updates `merge_jid` with the MergeWorker#jid.
# This helps tracking enqueued and ongoing merge jobs.
@@ -1218,7 +1231,7 @@ class MergeRequest < ApplicationRecord
}
end
- def mergeable?(skip_ci_check: false, skip_discussions_check: false, skip_approved_check: false, check_mergeability_retry_lease: false)
+ def mergeable?(skip_ci_check: false, skip_discussions_check: false, skip_approved_check: false, check_mergeability_retry_lease: false, skip_rebase_check: false)
return false unless mergeable_state?(
skip_ci_check: skip_ci_check,
skip_discussions_check: skip_discussions_check,
@@ -1227,7 +1240,7 @@ class MergeRequest < ApplicationRecord
check_mergeability(sync_retry_lease: check_mergeability_retry_lease)
- can_be_merged? && !should_be_rebased?
+ can_be_merged? && (!should_be_rebased? || skip_rebase_check)
end
def mergeability_checks
@@ -1593,7 +1606,7 @@ class MergeRequest < ApplicationRecord
# Since another process checks for matching merge request, we need
# to make it possible to detect whether the query should go to the
# primary.
- target_project.mark_primary_write_location
+ target_project.sticking.stick(:project, target_project.id)
end
def diverged_commits_count
@@ -1654,6 +1667,7 @@ class MergeRequest < ApplicationRecord
variables.append(key: 'CI_MERGE_REQUEST_ASSIGNEES', value: assignee_username_list) if assignees.present?
variables.append(key: 'CI_MERGE_REQUEST_MILESTONE', value: milestone.title) if milestone
variables.append(key: 'CI_MERGE_REQUEST_LABELS', value: label_names.join(',')) if labels.present?
+ variables.append(key: 'CI_MERGE_REQUEST_SQUASH_ON_MERGE', value: squash_on_merge?.to_s)
variables.concat(source_project_variables)
end
end
@@ -1831,7 +1845,7 @@ class MergeRequest < ApplicationRecord
def merged_commit_sha
return unless merged?
- sha = merge_commit_sha || squash_commit_sha || diff_head_sha
+ sha = super || merge_commit_sha || squash_commit_sha || diff_head_sha
sha.presence
end
@@ -1996,7 +2010,7 @@ class MergeRequest < ApplicationRecord
all_pipelines.for_sha_or_source_sha(diff_head_sha).first
end
- def etag_caching_enabled?
+ def real_time_notes_enabled?
true
end
@@ -2097,6 +2111,10 @@ class MergeRequest < ApplicationRecord
spammable_attribute_changed? && project.public?
end
+ def missing_required_squash?
+ !squash && target_project.squash_always?
+ end
+
private
attr_accessor :skip_fetch_ref
@@ -2141,6 +2159,7 @@ class MergeRequest < ApplicationRecord
variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH', value: source_project.full_path)
variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL', value: source_project.web_url)
variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME', value: source_branch.to_s)
+ variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_PROTECTED', value: ProtectedBranch.protected?(source_project, source_branch).to_s)
end
end
diff --git a/app/models/metrics/dashboard/annotation.rb b/app/models/metrics/dashboard/annotation.rb
deleted file mode 100644
index ac0fcb41089..00000000000
--- a/app/models/metrics/dashboard/annotation.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module Metrics
- module Dashboard
- class Annotation < ApplicationRecord
- include DeleteWithLimit
-
- self.table_name = 'metrics_dashboard_annotations'
-
- validates :starting_at, presence: true
- validates :description, presence: true, length: { maximum: 255 }
- validates :dashboard_path, presence: true, length: { maximum: 255 }
- validates :panel_xid, length: { maximum: 255 }
- validate :ending_at_after_starting_at
-
- scope :after, ->(after) { where('starting_at >= ?', after) }
- scope :before, ->(before) { where('starting_at <= ?', before) }
-
- scope :for_dashboard, ->(dashboard_path) { where(dashboard_path: dashboard_path) }
- scope :ending_before, ->(timestamp) { where('COALESCE(ending_at, starting_at) < ?', timestamp) }
-
- private
-
- # If annotation has NULL in ending_at column that indicates, that this annotation IS TIED TO SINGLE POINT
- # IN TIME designated by starting_at timestamp. It does NOT mean that annotation is ever going starting from
- # stating_at timestamp
- def ending_at_after_starting_at
- return if ending_at.blank? || starting_at.blank? || starting_at <= ending_at
-
- errors.add(:ending_at, s_("MetricsDashboardAnnotation|can't be before starting_at time"))
- end
- end
- end
-end
diff --git a/app/models/metrics/users_starred_dashboard.rb b/app/models/metrics/users_starred_dashboard.rb
deleted file mode 100644
index 07748eb1431..00000000000
--- a/app/models/metrics/users_starred_dashboard.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module Metrics
- class UsersStarredDashboard < ApplicationRecord
- self.table_name = 'metrics_users_starred_dashboards'
-
- belongs_to :user, inverse_of: :metrics_users_starred_dashboards
- belongs_to :project, inverse_of: :metrics_users_starred_dashboards
-
- validates :user_id, presence: true
- validates :project_id, presence: true
- validates :dashboard_path, presence: true, length: { maximum: 255 }
- validates :dashboard_path, uniqueness: { scope: %i[user_id project_id] }
-
- scope :for_project, ->(project) { where(project: project) }
- scope :for_project_dashboard, ->(project, path) { for_project(project).where(dashboard_path: path) }
- end
-end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 8de717fb61d..eb0da368c7b 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -134,7 +134,9 @@ class Milestone < ApplicationRecord
end
def participants
- User.joins(assigned_issues: :milestone).where(milestones: { id: id }).distinct
+ User.joins(assigned_issues: :milestone)
+ .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/422155')
+ .where(milestones: { id: id }).distinct
end
def self.sort_by_attribute(method)
diff --git a/app/models/ml/model_version.rb b/app/models/ml/model_version.rb
index 6d0e7c35865..e7fcde2cb5c 100644
--- a/app/models/ml/model_version.rb
+++ b/app/models/ml/model_version.rb
@@ -14,7 +14,7 @@ module Ml
belongs_to :model, class_name: 'Ml::Model'
belongs_to :project
- belongs_to :package, class_name: 'Packages::Package', optional: true
+ belongs_to :package, class_name: 'Packages::MlModel::Package', optional: true
delegate :name, to: :model
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index a7d03c3688a..ea0ea4de5b5 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -17,6 +17,9 @@ class Namespace < ApplicationRecord
include BlocksUnsafeSerialization
include Ci::NamespaceSettings
include Referable
+ include CrossDatabaseIgnoredTables
+
+ cross_database_ignore_tables %w[routes redirect_routes], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424277'
# Tells ActiveRecord not to store the full class name, in order to save some space
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69794
@@ -145,7 +148,6 @@ class Namespace < ApplicationRecord
after_update :force_share_with_group_lock_on_descendants, if: -> { saved_change_to_share_with_group_lock? && share_with_group_lock? }
after_update :expire_first_auto_devops_config_cache, if: -> { saved_change_to_auto_devops_enabled? }
after_update :move_dir, if: :saved_change_to_path_or_parent?, unless: -> { is_a?(Namespaces::ProjectNamespace) }
- after_destroy :rm_dir
after_save :reload_namespace_details
@@ -155,7 +157,6 @@ class Namespace < ApplicationRecord
# Legacy Storage specific hooks
- before_destroy(prepend: true) { prepare_for_destroy }
after_commit :expire_child_caches, on: :update, if: -> {
Feature.enabled?(:cached_route_lookups, self, type: :ops) &&
saved_change_to_name? || saved_change_to_path? || saved_change_to_parent_id?
@@ -166,7 +167,9 @@ class Namespace < ApplicationRecord
scope :sort_by_type, -> { order(arel_table[:type].asc.nulls_first) }
scope :include_route, -> { includes(:route) }
scope :by_parent, -> (parent) { where(parent_id: parent) }
+ scope :by_root_id, -> (root_id) { where('traversal_ids[1] IN (?)', root_id) }
scope :filter_by_path, -> (query) { where('lower(path) = :query', query: query.downcase) }
+ scope :in_organization, -> (organization) { where(organization: organization) }
scope :with_statistics, -> do
joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id')
@@ -231,16 +234,26 @@ class Namespace < ApplicationRecord
# query - The search query as a String.
#
# Returns an ActiveRecord::Relation.
- def search(query, include_parents: false, use_minimum_char_limit: true)
+ def search(query, include_parents: false, use_minimum_char_limit: true, exact_matches_first: false)
if include_parents
- without_project_namespaces
+ route_columns = [Route.arel_table[:path], Route.arel_table[:name]]
+ namespaces = without_project_namespaces
.where(id: Route.for_routable_type(Namespace.name)
.allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
- .fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name]],
+ .fuzzy_search(query, route_columns,
use_minimum_char_limit: use_minimum_char_limit)
.select(:source_id))
+
+ if exact_matches_first
+ namespaces = namespaces
+ .joins(:route)
+ .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
+ .order(exact_matches_first_sql(query, route_columns))
+ end
+
+ namespaces
else
- without_project_namespaces.fuzzy_search(query, [:path, :name], use_minimum_char_limit: use_minimum_char_limit)
+ without_project_namespaces.fuzzy_search(query, [:path, :name], use_minimum_char_limit: use_minimum_char_limit, exact_matches_first: exact_matches_first)
end
end
@@ -465,7 +478,7 @@ class Namespace < ApplicationRecord
return { scope: :group, status: auto_devops_enabled } unless auto_devops_enabled.nil?
strong_memoize(:first_auto_devops_config) do
- if has_parent?
+ if parent.present?
Rails.cache.fetch(first_auto_devops_config_cache_key_for(id), expires_in: 1.day) do
parent.first_auto_devops_config
end
@@ -751,7 +764,7 @@ class Namespace < ApplicationRecord
end
def reload_namespace_details
- return unless !project_namespace? && (previous_changes.keys & %w(description description_html cached_markdown_version)).any? && namespace_details.present?
+ return unless !project_namespace? && (previous_changes.keys & %w[description description_html cached_markdown_version]).any? && namespace_details.present?
namespace_details.reset
end
diff --git a/app/models/namespace/detail.rb b/app/models/namespace/detail.rb
index 6c825b5364f..a65027733e9 100644
--- a/app/models/namespace/detail.rb
+++ b/app/models/namespace/detail.rb
@@ -3,7 +3,6 @@
class Namespace::Detail < ApplicationRecord
include IgnorableColumns
- ignore_column :free_user_cap_over_limt_notified_at, remove_with: '15.7', remove_after: '2022-11-22'
ignore_column :dashboard_notification_at, remove_with: '16.5', remove_after: '2023-08-22'
ignore_column :dashboard_enforcement_at, remove_with: '16.5', remove_after: '2023-08-22'
ignore_column :next_over_limit_check_at, remove_with: '16.5', remove_after: '2023-08-22'
diff --git a/app/models/namespace/root_storage_statistics.rb b/app/models/namespace/root_storage_statistics.rb
index 8af0cf2767c..1d11bcb574c 100644
--- a/app/models/namespace/root_storage_statistics.rb
+++ b/app/models/namespace/root_storage_statistics.rb
@@ -2,7 +2,7 @@
class Namespace::RootStorageStatistics < ApplicationRecord
SNIPPETS_SIZE_STAT_NAME = 'snippets_size'
- STATISTICS_ATTRIBUTES = %W(
+ STATISTICS_ATTRIBUTES = %W[
storage_size
repository_size
wiki_size
@@ -12,7 +12,7 @@ class Namespace::RootStorageStatistics < ApplicationRecord
#{SNIPPETS_SIZE_STAT_NAME}
pipeline_artifacts_size
uploads_size
- ).freeze
+ ].freeze
self.primary_key = :namespace_id
@@ -36,7 +36,7 @@ class Namespace::RootStorageStatistics < ApplicationRecord
end
def self.namespace_statistics_attributes
- %w(storage_size dependency_proxy_size)
+ %w[storage_size dependency_proxy_size]
end
private
diff --git a/app/models/namespace/traversal_hierarchy.rb b/app/models/namespace/traversal_hierarchy.rb
index d2de85b5dd4..86fb562f4f4 100644
--- a/app/models/namespace/traversal_hierarchy.rb
+++ b/app/models/namespace/traversal_hierarchy.rb
@@ -39,9 +39,16 @@ class Namespace
AND namespaces.traversal_ids::bigint[] <> cte.traversal_ids
SQL
- Namespace.transaction do
- @root.lock!("FOR NO KEY UPDATE")
- Namespace.connection.exec_query(sql)
+ # Hint: when a user is created, it also creates a Namespaces::UserNamespace in
+ # `ensure_namespace_correct`. This method is then called within the same
+ # transaction of the user INSERT.
+ Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.temporary_ignore_tables_in_transaction(
+ %w[namespaces], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424279'
+ ) do
+ Namespace.transaction do
+ @root.lock!("FOR NO KEY UPDATE")
+ Namespace.connection.exec_query(sql)
+ end
end
rescue ActiveRecord::Deadlocked
db_deadlock_counter.increment(source: 'Namespace#sync_traversal_ids!')
diff --git a/app/models/namespaces/randomized_suffix_path.rb b/app/models/namespaces/randomized_suffix_path.rb
index 586d7bff5c3..b22ba789688 100644
--- a/app/models/namespaces/randomized_suffix_path.rb
+++ b/app/models/namespaces/randomized_suffix_path.rb
@@ -3,7 +3,7 @@
module Namespaces
class RandomizedSuffixPath
MAX_TRIES = 4
- LEADING_ZEROS = /^0+/.freeze
+ LEADING_ZEROS = /^0+/
def initialize(path)
@path = path
diff --git a/app/models/note.rb b/app/models/note.rb
index f1760a8dc4a..8fc45436dc7 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -28,7 +28,7 @@ class Note < ApplicationRecord
ignore_column :id_convert_to_bigint, remove_with: '16.3', remove_after: '2023-08-22'
- ISSUE_TASK_SYSTEM_NOTE_PATTERN = /\A.*marked\sthe\stask.+as\s(completed|incomplete).*\z/.freeze
+ ISSUE_TASK_SYSTEM_NOTE_PATTERN = /\A.*marked\sthe\stask.+as\s(completed|incomplete).*\z/
cache_markdown_field :note, pipeline: :note, issuable_reference_expansion_enabled: true
@@ -74,6 +74,7 @@ class Note < ApplicationRecord
attr_mentionable :note, pipeline: :note
participant :author
+ belongs_to :namespace
belongs_to :project
belongs_to :noteable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
belongs_to :author, class_name: "User"
@@ -104,6 +105,7 @@ class Note < ApplicationRecord
validates :note, presence: true
validates :note, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }
validates :project, presence: true, if: :for_project_noteable?
+ validates :namespace, presence: true
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
@@ -169,7 +171,7 @@ class Note < ApplicationRecord
end
end
- scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) }
+ scope :diff_notes, -> { where(type: %w[LegacyDiffNote DiffNote]) }
scope :new_diff_notes, -> { where(type: 'DiffNote') }
scope :non_diff_notes, -> { where(type: NON_DIFF_NOTE_TYPES) }
@@ -193,7 +195,7 @@ class Note < ApplicationRecord
scope :for_note_or_capitalized_note, ->(text) { where(note: [text, text.capitalize]) }
scope :like_note_or_capitalized_note, ->(text) { where('(note LIKE ? OR note LIKE ?)', text, text.capitalize) }
- before_validation :nullify_blank_type, :nullify_blank_line_code
+ before_validation :ensure_namespace_id, :nullify_blank_type, :nullify_blank_line_code
# Syncs `confidential` with `internal` as we rename the column.
# https://gitlab.com/gitlab-org/gitlab/-/issues/367923
before_create :set_internal_flag
@@ -205,7 +207,7 @@ class Note < ApplicationRecord
after_commit :trigger_note_subscription_create, on: :create
after_commit :trigger_note_subscription_update, on: :update
after_commit :trigger_note_subscription_destroy, on: :destroy
- after_commit :expire_etag_cache, unless: :importing?
+ after_commit :broadcast_noteable_notes_changed, unless: :importing?
def trigger_note_subscription_create
return unless trigger_note_subscription?
@@ -589,8 +591,8 @@ class Note < ApplicationRecord
update_columns(attributes_to_update)
end
- def expire_etag_cache
- noteable&.expire_note_etag_cache
+ def broadcast_noteable_notes_changed
+ noteable&.broadcast_notes_changed
end
def touch(*args, **kwargs)
@@ -825,6 +827,16 @@ class Note < ApplicationRecord
project.repository.keep_around(self.commit_id)
end
+ def ensure_namespace_id
+ return if namespace_id.present? && !noteable_changed? && !project_changed?
+
+ self.namespace_id = if for_project_noteable?
+ project&.project_namespace_id
+ elsif for_personal_snippet?
+ noteable&.author&.namespace&.id
+ end
+ end
+
def nullify_blank_type
self.type = nil if self.type.blank?
end
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index cde7b92e74a..eb4fa9ac474 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -60,7 +60,7 @@ class NotificationSetting < ApplicationRecord
end
def self.allowed_fields(source = nil)
- NotificationSetting.email_events(source).dup + %i(level notification_email)
+ NotificationSetting.email_events(source).dup + %i[level notification_email]
end
def email_events
diff --git a/app/models/operations/feature_flag.rb b/app/models/operations/feature_flag.rb
index 01db0a5cf8b..b93537e0d1e 100644
--- a/app/models/operations/feature_flag.rb
+++ b/app/models/operations/feature_flag.rb
@@ -52,7 +52,7 @@ module Operations
class << self
def preload_relations
- preload(strategies: :scopes)
+ preload(strategies: [:scopes, :user_list])
end
def for_unleash_client(project, environment)
diff --git a/app/models/organizations/organization.rb b/app/models/organizations/organization.rb
index 9f2119949fb..893b08d7872 100644
--- a/app/models/organizations/organization.rb
+++ b/app/models/organizations/organization.rb
@@ -10,6 +10,7 @@ module Organizations
has_many :namespaces
has_many :groups
+ has_many :projects
has_one :settings, class_name: "OrganizationSetting"
@@ -38,7 +39,7 @@ module Organizations
end
def user?(user)
- users.exists?(user.id)
+ organization_users.exists?(user: user)
end
private
diff --git a/app/models/packages/debian.rb b/app/models/packages/debian.rb
index 2b8d0a4f51e..1fe4e28146e 100644
--- a/app/models/packages/debian.rb
+++ b/app/models/packages/debian.rb
@@ -4,13 +4,13 @@ module Packages
module Debian
TEMPORARY_PACKAGE_NAME = 'debian-temporary-package'
- DISTRIBUTION_REGEX = %r{[a-z0-9][a-z0-9.-]*}i.freeze
+ DISTRIBUTION_REGEX = %r{[a-z0-9][a-z0-9.-]*}i
COMPONENT_REGEX = DISTRIBUTION_REGEX.freeze
- ARCHITECTURE_REGEX = %r{[a-z0-9][-a-z0-9]*}.freeze
+ ARCHITECTURE_REGEX = %r{[a-z0-9][-a-z0-9]*}
- LETTER_REGEX = %r{(lib)?[a-z0-9]}.freeze
+ LETTER_REGEX = %r{(lib)?[a-z0-9]}
- EMPTY_FILE_SHA256 = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'.freeze
+ EMPTY_FILE_SHA256 = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
INCOMING_PACKAGE_NAME = 'incoming'
diff --git a/app/models/packages/debian/file_entry.rb b/app/models/packages/debian/file_entry.rb
index 7ea0dfe8765..4ac621dcbd4 100644
--- a/app/models/packages/debian/file_entry.rb
+++ b/app/models/packages/debian/file_entry.rb
@@ -6,7 +6,7 @@ module Packages
include ActiveModel::Model
DIGESTS = %i[md5 sha1 sha256].freeze
- FILENAME_REGEX = %r{\A[a-zA-Z0-9][a-zA-Z0-9_.~+-]*\z}.freeze
+ FILENAME_REGEX = %r{\A[a-zA-Z0-9][a-zA-Z0-9_.~+-]*\z}
attr_accessor :filename,
:size,
diff --git a/app/models/packages/dependency_link.rb b/app/models/packages/dependency_link.rb
index 51018602bdc..400b4cce208 100644
--- a/app/models/packages/dependency_link.rb
+++ b/app/models/packages/dependency_link.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
class Packages::DependencyLink < ApplicationRecord
+ include EachBatch
+
belongs_to :package, inverse_of: :dependency_links
belongs_to :dependency, inverse_of: :dependency_links, class_name: 'Packages::Dependency'
has_one :nuget_metadatum, inverse_of: :dependency_link, class_name: 'Packages::Nuget::DependencyLinkMetadatum'
@@ -14,6 +16,32 @@ class Packages::DependencyLink < ApplicationRecord
scope :with_dependency_type, ->(dependency_type) { where(dependency_type: dependency_type) }
scope :includes_dependency, -> { includes(:dependency) }
scope :for_package, ->(package) { where(package_id: package.id) }
+ scope :for_packages, ->(packages) { where(package: packages) }
scope :preload_dependency, -> { preload(:dependency) }
scope :preload_nuget_metadatum, -> { preload(:nuget_metadatum) }
+ scope :select_dependency_id, -> { select(:dependency_id) }
+
+ def self.dependency_ids_grouped_by_type(packages)
+ inner_query = where(package_id: packages)
+ .select('
+ package_id,
+ dependency_type,
+ ARRAY_AGG(dependency_id) as dependency_ids
+ ')
+ .group(:package_id, :dependency_type)
+
+ cte = Gitlab::SQL::CTE.new(:dependency_links_cte, inner_query)
+ cte_alias = cte.table.alias(table_name)
+
+ with(cte.to_arel)
+ .select('
+ package_id,
+ JSON_OBJECT_AGG(
+ dependency_type,
+ dependency_ids
+ ) AS dependency_ids_by_type
+ ')
+ .from(cte_alias)
+ .group(:package_id)
+ end
end
diff --git a/app/models/packages/ml_model/package.rb b/app/models/packages/ml_model/package.rb
new file mode 100644
index 00000000000..de2b5f8f2a8
--- /dev/null
+++ b/app/models/packages/ml_model/package.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Packages
+ module MlModel
+ class Package < Packages::Package
+ has_one :model_version, class_name: "Ml::ModelVersion", inverse_of: :package
+
+ validates :name,
+ format: Gitlab::Regex.ml_model_name_regex,
+ presence: true,
+ length: { maximum: 255 }
+
+ validates :version,
+ format: Gitlab::Regex.semver_regex,
+ presence: true,
+ length: { maximum: 255 }
+ end
+ end
+end
diff --git a/app/models/packages/nuget/metadatum.rb b/app/models/packages/nuget/metadatum.rb
index e7cf4528f16..1025af0fd24 100644
--- a/app/models/packages/nuget/metadatum.rb
+++ b/app/models/packages/nuget/metadatum.rb
@@ -15,8 +15,7 @@ class Packages::Nuget::Metadatum < ApplicationRecord
validates :icon_url, public_url: { allow_blank: true }, length: { maximum: MAX_URL_LENGTH }
validates :authors, presence: true, length: { maximum: MAX_AUTHORS_LENGTH }
validates :description, presence: true, length: { maximum: MAX_DESCRIPTION_LENGTH }
- validates :normalized_version, presence: true,
- if: -> { Feature.enabled?(:nuget_normalized_version, package&.project) }
+ validates :normalized_version, presence: true
validate :ensure_nuget_package_type
diff --git a/app/models/packages/nuget/symbol.rb b/app/models/packages/nuget/symbol.rb
new file mode 100644
index 00000000000..643b5552d84
--- /dev/null
+++ b/app/models/packages/nuget/symbol.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class Symbol < ApplicationRecord
+ include FileStoreMounter
+
+ belongs_to :package, -> { where(package_type: :nuget) }, inverse_of: :nuget_symbols
+
+ delegate :project_id, to: :package
+
+ validates :package, :file, :file_path, :signature, :object_storage_key, :size, presence: true
+ validates :signature, uniqueness: { scope: :file_path }
+ validates :object_storage_key, uniqueness: true
+
+ mount_file_store_uploader SymbolUploader
+
+ before_validation :set_object_storage_key, on: :create
+
+ private
+
+ def set_object_storage_key
+ return unless project_id && signature
+
+ self.object_storage_key = Gitlab::HashedPath.new(
+ 'packages', 'nuget', package_id, 'symbols', OpenSSL::Digest::SHA256.hexdigest(signature),
+ root_hash: project_id
+ ).to_s
+ end
+ end
+ end
+end
diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb
index b09911f4216..02e3908b3bf 100644
--- a/app/models/packages/package.rb
+++ b/app/models/packages/package.rb
@@ -7,6 +7,7 @@ class Packages::Package < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include Packages::Installable
include Packages::Downloadable
+ include EnumInheritance
DISPLAYABLE_STATUSES = [:default, :error].freeze
INSTALLABLE_STATUSES = [:default, :hidden].freeze
@@ -48,6 +49,7 @@ class Packages::Package < ApplicationRecord
has_one :pypi_metadatum, inverse_of: :package, class_name: 'Packages::Pypi::Metadatum'
has_one :maven_metadatum, inverse_of: :package, class_name: 'Packages::Maven::Metadatum'
has_one :nuget_metadatum, inverse_of: :package, class_name: 'Packages::Nuget::Metadatum'
+ has_many :nuget_symbols, inverse_of: :package, class_name: 'Packages::Nuget::Symbol'
has_one :composer_metadatum, inverse_of: :package, class_name: 'Packages::Composer::Metadatum'
has_one :rubygems_metadatum, inverse_of: :package, class_name: 'Packages::Rubygems::Metadatum'
has_one :rpm_metadatum, inverse_of: :package, class_name: 'Packages::Rpm::Metadatum'
@@ -179,11 +181,7 @@ class Packages::Package < ApplicationRecord
scope :preload_conan_metadatum, -> { preload(:conan_metadatum) }
scope :with_npm_scope, ->(scope) do
- if Feature.enabled?(:npm_package_registry_fix_group_path_validation)
- npm.where("position('/' in packages_packages.name) > 0 AND split_part(packages_packages.name, '/', 1) = :package_scope", package_scope: "@#{sanitize_sql_like(scope)}")
- else
- npm.where("name ILIKE :package_name", package_name: "@#{sanitize_sql_like(scope)}/%")
- end
+ npm.where("position('/' in packages_packages.name) > 0 AND split_part(packages_packages.name, '/', 1) = :package_scope", package_scope: "@#{sanitize_sql_like(scope)}")
end
scope :without_nuget_temporary_name, -> { where.not(name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
@@ -220,6 +218,12 @@ class Packages::Package < ApplicationRecord
joins(:project).reorder(keyset_order)
end
+ def self.inheritance_column = 'package_type'
+
+ def self.inheritance_column_to_class_map = {
+ ml_model: 'Packages::MlModel::Package'
+ }.freeze
+
def self.only_maven_packages_with_path(path, use_cte: false)
if use_cte
# This is an optimization fence which assumes that looking up the Metadatum record by path (globally)
diff --git a/app/models/packages/protection.rb b/app/models/packages/protection.rb
new file mode 100644
index 00000000000..ebaecf89992
--- /dev/null
+++ b/app/models/packages/protection.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Packages
+ module Protection
+ def self.table_name_prefix
+ 'packages_protection_'
+ end
+ end
+end
diff --git a/app/models/packages/protection/rule.rb b/app/models/packages/protection/rule.rb
new file mode 100644
index 00000000000..bb65be92b90
--- /dev/null
+++ b/app/models/packages/protection/rule.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Packages
+ module Protection
+ class Rule < ApplicationRecord
+ enum package_type: Packages::Package.package_types.slice(:npm)
+
+ belongs_to :project, inverse_of: :package_protection_rules
+
+ validates :package_name_pattern, presence: true, uniqueness: { scope: [:project_id, :package_type] },
+ length: { maximum: 255 }
+ validates :package_type, presence: true
+ validates :push_protected_up_to_access_level, presence: true,
+ inclusion: { in: [
+ Gitlab::Access::DEVELOPER,
+ Gitlab::Access::MAINTAINER,
+ Gitlab::Access::OWNER
+ ] }
+ end
+ end
+end
diff --git a/app/models/pages/lookup_path.rb b/app/models/pages/lookup_path.rb
index 2ffb2e84cbf..e8becc833ca 100644
--- a/app/models/pages/lookup_path.rb
+++ b/app/models/pages/lookup_path.rb
@@ -35,7 +35,7 @@ module Pages
{
type: 'zip',
path: deployment.file.url_or_file_path(
- expire_at: ::Gitlab::Pages::CacheControl::DEPLOYMENT_EXPIRATION.from_now
+ expire_at: ::Gitlab::Pages::DEPLOYMENT_EXPIRATION.from_now
),
global_id: global_id,
sha256: deployment.file_sha256,
diff --git a/app/models/pages/virtual_domain.rb b/app/models/pages/virtual_domain.rb
index fafbe449c8c..0a64e91bf60 100644
--- a/app/models/pages/virtual_domain.rb
+++ b/app/models/pages/virtual_domain.rb
@@ -2,9 +2,8 @@
module Pages
class VirtualDomain
- def initialize(projects:, cache: nil, trim_prefix: nil, domain: nil)
+ def initialize(projects:, trim_prefix: nil, domain: nil)
@projects = projects
- @cache = cache
@trim_prefix = trim_prefix
@domain = domain
end
@@ -18,23 +17,19 @@ module Pages
end
def lookup_paths
- paths = projects.map do |project|
- project.pages_lookup_path(trim_prefix: trim_prefix, domain: domain)
- end
-
- # TODO: remove in https://gitlab.com/gitlab-org/gitlab/-/issues/328715
- paths = paths.select(&:source)
-
- paths.sort_by(&:prefix).reverse
- end
-
- # cache_key is required by #present_cached in ::API::Internal::Pages
- def cache_key
- @cache_key ||= cache&.cache_key
+ projects
+ .map { |project| lookup_paths_for(project) }
+ .select(&:source) # TODO: remove in https://gitlab.com/gitlab-org/gitlab/-/issues/328715
+ .sort_by(&:prefix)
+ .reverse
end
private
- attr_reader :projects, :trim_prefix, :domain, :cache
+ attr_reader :projects, :trim_prefix, :domain
+
+ def lookup_paths_for(project)
+ Pages::LookupPath.new(project, trim_prefix: trim_prefix, domain: domain)
+ end
end
end
diff --git a/app/models/pages_deployment.rb b/app/models/pages_deployment.rb
index ec2293fa032..de7b2416258 100644
--- a/app/models/pages_deployment.rb
+++ b/app/models/pages_deployment.rb
@@ -11,13 +11,16 @@ class PagesDeployment < ApplicationRecord
attribute :file_store, :integer, default: -> { ::Pages::DeploymentUploader.default_store }
belongs_to :project, optional: false
+
+ # ci_build is optional, because PagesDeployment must live even if its build/pipeline is removed.
belongs_to :ci_build, class_name: 'Ci::Build', optional: true
- scope :older_than, -> (id) { where('id < ?', id) }
+ scope :older_than, ->(id) { where('id < ?', id) }
scope :migrated_from_legacy_storage, -> { where(file: MIGRATED_FILE_NAME) }
scope :with_files_stored_locally, -> { where(file_store: ::ObjectStorage::Store::LOCAL) }
scope :with_files_stored_remotely, -> { where(file_store: ::ObjectStorage::Store::REMOTE) }
scope :project_id_in, ->(ids) { where(project_id: ids) }
+ scope :active, -> { where(deleted_at: nil) }
validates :file, presence: true
validates :file_store, presence: true, inclusion: { in: ObjectStorage::SUPPORTED_STORES }
@@ -32,6 +35,14 @@ class PagesDeployment < ApplicationRecord
skip_callback :save, :after, :store_file!
after_commit :store_file_after_commit!, on: [:create, :update]
+ def self.deactivate_deployments_older_than(deployment, time: nil)
+ now = Time.now.utc
+ active
+ .older_than(deployment.id)
+ .where(project_id: deployment.project_id, path_prefix: deployment.path_prefix)
+ .update_all(updated_at: now, deleted_at: time || now)
+ end
+
def migrated?
file.filename == MIGRATED_FILE_NAME
end
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 88d7f0f972a..b86bc761cc1 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -9,6 +9,8 @@ class PagesDomain < ApplicationRecord
VERIFICATION_THRESHOLD = 3.days.freeze
SSL_RENEWAL_THRESHOLD = 30.days.freeze
+ MAX_CERTIFICATE_KEY_LENGTH = 8192
+
enum certificate_source: { user_provided: 0, gitlab_provided: 1 }, _prefix: :certificate
enum scope: { instance: 0, group: 1, project: 2 }, _prefix: :scope, _default: :project
enum usage: { pages: 0, serverless: 1 }, _prefix: :usage, _default: :pages
@@ -34,6 +36,7 @@ class PagesDomain < ApplicationRecord
validate :validate_matching_key, if: ->(domain) { domain.certificate.present? || domain.key.present? }
validate :validate_intermediates, if: ->(domain) { domain.certificate.present? && domain.certificate_changed? }
validate :validate_custom_domain_count_per_project, on: :create
+ validate :max_certificate_key_length, if: ->(domain) { domain.key.present? }
attribute :auto_ssl_enabled, default: -> { ::Gitlab::LetsEncrypt.enabled? }
attribute :wildcard, default: false
@@ -234,6 +237,16 @@ class PagesDomain < ApplicationRecord
private
+ def max_certificate_key_length
+ return unless pkey.is_a?(OpenSSL::PKey::RSA)
+ return if pkey.to_s.bytesize <= MAX_CERTIFICATE_KEY_LENGTH
+
+ errors.add(
+ :key,
+ s_("PagesDomain|Certificate Key is too long. (Max %d bytes)") % MAX_CERTIFICATE_KEY_LENGTH
+ )
+ end
+
def set_verification_code
return if self.verification_code.present?
diff --git a/app/models/performance_monitoring/prometheus_metric.rb b/app/models/performance_monitoring/prometheus_metric.rb
deleted file mode 100644
index d67b1809d93..00000000000
--- a/app/models/performance_monitoring/prometheus_metric.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-module PerformanceMonitoring
- class PrometheusMetric
- include ActiveModel::Model
-
- attr_accessor :id, :unit, :label, :query, :query_range
-
- validates :unit, presence: true
- validates :query, presence: true, unless: :query_range
- validates :query_range, presence: true, unless: :query
-
- class << self
- def from_json(json_content)
- build_from_hash(json_content).tap(&:validate!)
- end
-
- private
-
- def build_from_hash(attributes)
- return new unless attributes.is_a?(Hash)
-
- new(
- id: attributes['id'],
- unit: attributes['unit'],
- label: attributes['label'],
- query: attributes['query'],
- query_range: attributes['query_range']
- )
- end
- end
- end
-end
diff --git a/app/models/performance_monitoring/prometheus_panel.rb b/app/models/performance_monitoring/prometheus_panel.rb
deleted file mode 100644
index b33c09001ae..00000000000
--- a/app/models/performance_monitoring/prometheus_panel.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-module PerformanceMonitoring
- class PrometheusPanel
- include ActiveModel::Model
-
- attr_accessor :type, :title, :y_label, :weight, :metrics, :y_axis, :max_value
-
- validates :title, presence: true
- validates :metrics, array_members: { member_class: PerformanceMonitoring::PrometheusMetric }
-
- class << self
- def from_json(json_content)
- build_from_hash(json_content).tap(&:validate!)
- end
-
- private
-
- def build_from_hash(attributes)
- return new unless attributes.is_a?(Hash)
-
- new(
- type: attributes['type'],
- title: attributes['title'],
- y_label: attributes['y_label'],
- weight: attributes['weight'],
- metrics: initialize_children_collection(attributes['metrics'])
- )
- end
-
- def initialize_children_collection(children)
- return unless children.is_a?(Array)
-
- children.map { |metrics| PerformanceMonitoring::PrometheusMetric.from_json(metrics) }
- end
- end
-
- def id(group_title)
- Digest::SHA2.hexdigest([group_title, type, title].join)
- end
- end
-end
diff --git a/app/models/performance_monitoring/prometheus_panel_group.rb b/app/models/performance_monitoring/prometheus_panel_group.rb
deleted file mode 100644
index 7f3d2a1b8f4..00000000000
--- a/app/models/performance_monitoring/prometheus_panel_group.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-module PerformanceMonitoring
- class PrometheusPanelGroup
- include ActiveModel::Model
-
- attr_accessor :group, :priority, :panels
-
- validates :group, presence: true
- validates :panels, array_members: { member_class: PerformanceMonitoring::PrometheusPanel }
-
- class << self
- def from_json(json_content)
- build_from_hash(json_content).tap(&:validate!)
- end
-
- private
-
- def build_from_hash(attributes)
- return new unless attributes.is_a?(Hash)
-
- new(
- group: attributes['group'],
- priority: attributes['priority'],
- panels: initialize_children_collection(attributes['panels'])
- )
- end
-
- def initialize_children_collection(children)
- return unless children.is_a?(Array)
-
- children.map { |panels| PerformanceMonitoring::PrometheusPanel.from_json(panels) }
- end
- end
- end
-end
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 08f725de980..4dfe7252a0c 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -14,7 +14,7 @@ class PersonalAccessToken < ApplicationRecord
format_with_prefix: :prefix_from_application_current_settings
# PATs are 20 characters + optional configurable settings prefix (0..20)
- TOKEN_LENGTH_RANGE = (20..40).freeze
+ TOKEN_LENGTH_RANGE = (20..40)
MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS = 365
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
diff --git a/app/models/plan.rb b/app/models/plan.rb
index 22c1201421c..9ab22bc045a 100644
--- a/app/models/plan.rb
+++ b/app/models/plan.rb
@@ -5,6 +5,8 @@ class Plan < MainClusterwide::ApplicationRecord
has_one :limits, class_name: 'PlanLimits'
+ scope :by_name, ->(name) { where(name: name) }
+
ALL_PLANS = [DEFAULT].freeze
DEFAULT_PLANS = [DEFAULT].freeze
private_constant :ALL_PLANS, :DEFAULT_PLANS
diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb
index bc3898fafe7..7d043bae91c 100644
--- a/app/models/pool_repository.rb
+++ b/app/models/pool_repository.rb
@@ -8,15 +8,15 @@ class PoolRepository < ApplicationRecord
include AfterCommitQueue
belongs_to :source_project, class_name: 'Project'
- validates :source_project, presence: true
has_many :member_projects, class_name: 'Project'
after_create :set_disk_path
scope :by_source_project, ->(project) { where(source_project: project) }
- scope :by_source_project_and_shard_name, ->(project, shard_name) do
- by_source_project(project)
+ scope :by_disk_path, ->(disk_path) { where(disk_path: disk_path) }
+ scope :by_disk_path_and_shard_name, ->(disk_path, shard_name) do
+ by_disk_path(disk_path)
.for_repository_storage(shard_name)
end
@@ -101,8 +101,8 @@ class PoolRepository < ApplicationRecord
@object_pool ||= Gitlab::Git::ObjectPool.new(
shard.name,
disk_path + '.git',
- source_project.repository.raw,
- source_project.full_path
+ source_project&.repository&.raw,
+ source_project&.full_path
)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index ad8757880fd..68196f0a757 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -44,9 +44,12 @@ class Project < ApplicationRecord
include IssueParent
include UpdatedAtFilterable
include IgnorableColumns
+ include CrossDatabaseIgnoredTables
ignore_column :emails_disabled, remove_with: '16.3', remove_after: '2023-08-22'
+ cross_database_ignore_tables %w[routes redirect_routes], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424277'
+
extend Gitlab::Cache::RequestCache
extend Gitlab::Utils::Override
@@ -68,10 +71,10 @@ class Project < ApplicationRecord
}.freeze
VALID_IMPORT_PORTS = [80, 443].freeze
- VALID_IMPORT_PROTOCOLS = %w(http https git).freeze
+ VALID_IMPORT_PROTOCOLS = %w[http https git].freeze
VALID_MIRROR_PORTS = [22, 80, 443].freeze
- VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
+ VALID_MIRROR_PROTOCOLS = %w[http https ssh git].freeze
SORTING_PREFERENCE_FIELD = :projects_sort
MAX_BUILD_TIMEOUT = 1.month
@@ -81,6 +84,8 @@ class Project < ApplicationRecord
MAX_SUGGESTIONS_TEMPLATE_LENGTH = 255
MAX_COMMIT_TEMPLATE_LENGTH = 500
+ INSTANCE_RUNNER_RUNNING_JOBS_MAX_BUCKET = 5
+
DEFAULT_MERGE_COMMIT_TEMPLATE = <<~MSG.rstrip.freeze
Merge branch '%{source_branch}' into '%{target_branch}'
@@ -163,6 +168,7 @@ class Project < ApplicationRecord
# Relations
belongs_to :pool_repository
belongs_to :creator, class_name: 'User'
+ belongs_to :organization, class_name: 'Organizations::Organization'
belongs_to :group, -> { where(type: Group.sti_name) }, foreign_key: 'namespace_id'
belongs_to :namespace
# Sync deletion via DB Trigger to ensure we do not have
@@ -265,6 +271,9 @@ class Project < ApplicationRecord
dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :npm_metadata_caches, class_name: 'Packages::Npm::MetadataCache'
has_one :packages_cleanup_policy, class_name: 'Packages::Cleanup::Policy', inverse_of: :project
+ has_many :package_protection_rules,
+ class_name: 'Packages::Protection::Rule',
+ inverse_of: :project
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -273,7 +282,6 @@ class Project < ApplicationRecord
has_one :project_repository, inverse_of: :project
has_one :incident_management_setting, inverse_of: :project, class_name: 'IncidentManagement::ProjectIncidentManagementSetting'
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
- has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
has_one :grafana_integration, inverse_of: :project
has_one :project_setting, inverse_of: :project, autosave: true
has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
@@ -336,7 +344,15 @@ class Project < ApplicationRecord
primary_key: :project_namespace_id, foreign_key: :member_namespace_id, inverse_of: :project,
class_name: 'ProjectMember'
- has_many :users, through: :project_members
+ has_many :users, -> { allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/422405") },
+ through: :project_members
+ has_many :maintainers,
+ -> do
+ allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/422405")
+ .where(members: { access_level: Gitlab::Access::MAINTAINER })
+ end,
+ through: :project_members,
+ source: :user
has_many :project_callouts, class_name: 'Users::ProjectCallout', foreign_key: :project_id
@@ -370,8 +386,6 @@ class Project < ApplicationRecord
has_many :prometheus_metrics
has_many :prometheus_alerts, inverse_of: :project
has_many :prometheus_alert_events, inverse_of: :project
- has_many :self_managed_prometheus_alert_events, inverse_of: :project
- has_many :metrics_users_starred_dashboards, class_name: 'Metrics::UsersStarredDashboard', inverse_of: :project
has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :project
has_many :alert_management_http_integrations, class_name: 'AlertManagement::HttpIntegration', inverse_of: :project
@@ -476,7 +490,6 @@ class Project < ApplicationRecord
accepts_nested_attributes_for :incident_management_setting, update_only: true
accepts_nested_attributes_for :error_tracking_setting, update_only: true
- accepts_nested_attributes_for :metrics_setting, update_only: true, allow_destroy: true
accepts_nested_attributes_for :grafana_integration, update_only: true, allow_destroy: true
accepts_nested_attributes_for :prometheus_integration, update_only: true
accepts_nested_attributes_for :alerting_setting, update_only: true
@@ -492,11 +505,6 @@ class Project < ApplicationRecord
delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_owner, :add_role
end
- with_options to: :metrics_setting, allow_nil: true, prefix: true do
- delegate :external_dashboard_url
- delegate :dashboard_timezone
- end
-
with_options to: :namespace do
delegate :actual_limits, :actual_plan_name, :actual_plan, :root_ancestor, allow_nil: true
delegate :maven_package_requests_forwarding, :pypi_package_requests_forwarding, :npm_package_requests_forwarding
@@ -1282,7 +1290,7 @@ class Project < ApplicationRecord
def design_repository
strong_memoize(:design_repository) do
- Gitlab::GlRepository::DESIGN.repository_for(self)
+ find_or_create_design_management_repository.repository
end
end
@@ -1665,7 +1673,7 @@ class Project < ApplicationRecord
return unless Gitlab::Email::IncomingEmail.supports_issue_creation? && author
# check since this can come from a request parameter
- return unless %w(issue merge_request).include?(address_type)
+ return unless %w[issue merge_request].include?(address_type)
author.ensure_incoming_email_token!
@@ -2757,10 +2765,6 @@ class Project < ApplicationRecord
[]
end
- def mark_primary_write_location
- self.class.sticking.mark_primary_write_location(:project, self.id)
- end
-
def toggle_ci_cd_settings!(settings_attribute)
ci_cd_settings.toggle!(settings_attribute)
end
@@ -2842,7 +2846,7 @@ class Project < ApplicationRecord
return if old_pool_repository.blank?
return if pool_repository_shard_matches_repository?(old_pool_repository)
- new_pool_repository = PoolRepository.by_source_project_and_shard_name(old_pool_repository.source_project, repository_storage).take!
+ new_pool_repository = PoolRepository.by_disk_path_and_shard_name(old_pool_repository.disk_path, repository_storage).take!
update!(pool_repository: new_pool_repository)
old_pool_repository.unlink_repository(repository, disconnect: !pending_delete?)
@@ -2871,10 +2875,6 @@ class Project < ApplicationRecord
recipients
end
- def pages_lookup_path(trim_prefix: nil, domain: nil)
- Pages::LookupPath.new(self, trim_prefix: trim_prefix, domain: domain)
- end
-
def closest_setting(name)
setting = read_attribute(name)
setting = closest_namespace_setting(name) if setting.nil?
@@ -2954,10 +2954,6 @@ class Project < ApplicationRecord
jira_imports.last
end
- def metrics_setting
- super || build_metrics_setting
- end
-
def service_desk_enabled
Gitlab::ServiceDesk.enabled?(project: self)
end
@@ -2965,7 +2961,11 @@ class Project < ApplicationRecord
alias_method :service_desk_enabled?, :service_desk_enabled
def service_desk_address
- service_desk_custom_address || service_desk_incoming_address
+ service_desk_custom_address || service_desk_system_address
+ end
+
+ def service_desk_system_address
+ service_desk_alias_address || service_desk_incoming_address
end
def service_desk_incoming_address
@@ -2977,7 +2977,7 @@ class Project < ApplicationRecord
config.address&.gsub(wildcard, "#{full_path_slug}-#{default_service_desk_suffix}")
end
- def service_desk_custom_address
+ def service_desk_alias_address
return unless Gitlab::Email::ServiceDeskEmail.enabled?
key = service_desk_setting&.project_key || default_service_desk_suffix
@@ -2985,6 +2985,13 @@ class Project < ApplicationRecord
Gitlab::Email::ServiceDeskEmail.address_for_key("#{full_path_slug}-#{key}")
end
+ def service_desk_custom_address
+ return unless Feature.enabled?(:service_desk_custom_email, self)
+ return unless service_desk_setting&.custom_email_enabled?
+
+ service_desk_setting.custom_email
+ end
+
def default_service_desk_suffix
"#{id}-issue-"
end
@@ -3261,6 +3268,10 @@ class Project < ApplicationRecord
group.crm_enabled?
end
+ def supports_lock_on_merge?
+ group&.supports_lock_on_merge? || ::Feature.enabled?(:enforce_locked_labels_on_merge, self, type: :ops)
+ end
+
def path_availability
base, _, host = path.partition('.')
@@ -3270,6 +3281,13 @@ class Project < ApplicationRecord
errors.add(:path, s_('Project|already in use'))
end
+ def instance_runner_running_jobs_count
+ # excluding currently started job
+ ::Ci::RunningBuild.instance_type.where(project_id: self.id)
+ .limit(INSTANCE_RUNNER_RUNNING_JOBS_MAX_BUCKET + 1).count - 1
+ end
+ strong_memoize_attr :instance_runner_running_jobs_count
+
private
# overridden in EE
@@ -3483,11 +3501,11 @@ class Project < ApplicationRecord
end
def sync_project_namespace?
- (changes.keys & %w(name path namespace_id namespace visibility_level shared_runners_enabled)).any? && project_namespace.present?
+ (changes.keys & %w[name path namespace_id namespace visibility_level shared_runners_enabled]).any? && project_namespace.present?
end
def reload_project_namespace_details
- return unless (previous_changes.keys & %w(description description_html cached_markdown_version)).any? && project_namespace.namespace_details.present?
+ return unless (previous_changes.keys & %w[description description_html cached_markdown_version]).any? && project_namespace.namespace_details.present?
project_namespace.namespace_details.reset
end
diff --git a/app/models/project_authorization.rb b/app/models/project_authorization.rb
index 99128d3cddf..c328e7d37c8 100644
--- a/app/models/project_authorization.rb
+++ b/app/models/project_authorization.rb
@@ -11,6 +11,11 @@ class ProjectAuthorization < ApplicationRecord
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
validates :user, uniqueness: { scope: :project }, presence: true
+ scope :non_guests, -> { where('access_level > ?', ::Gitlab::Access::GUEST) }
+
+ # TODO: To be removed after https://gitlab.com/gitlab-org/gitlab/-/issues/418205
+ before_create :assign_is_unique
+
def self.select_from_union(relations)
from_union(relations)
.select(['project_id', 'MAX(access_level) AS access_level'])
@@ -25,6 +30,12 @@ class ProjectAuthorization < ApplicationRecord
def self.insert_all(attributes)
super(attributes, unique_by: connection.schema_cache.primary_keys(table_name))
end
+
+ private
+
+ def assign_is_unique
+ self.is_unique = true
+ end
end
ProjectAuthorization.prepend_mod_with('ProjectAuthorization')
diff --git a/app/models/project_authorizations/changes.rb b/app/models/project_authorizations/changes.rb
index 1d717950c1c..1f0cec1a50c 100644
--- a/app/models/project_authorizations/changes.rb
+++ b/app/models/project_authorizations/changes.rb
@@ -90,6 +90,8 @@ module ProjectAuthorizations
log_details(entire_size: attributes.size, batch_size: BATCH_SIZE) if add_delay
attributes.each_slice(BATCH_SIZE) do |attributes_batch|
+ attributes_batch.each { |attrs| attrs[:is_unique] = true }
+
ProjectAuthorization.insert_all(attributes_batch)
perform_delay if add_delay
end
diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb
index cc9003423be..8d049b8d1b1 100644
--- a/app/models/project_ci_cd_setting.rb
+++ b/app/models/project_ci_cd_setting.rb
@@ -19,6 +19,7 @@ class ProjectCiCdSetting < ApplicationRecord
attribute :forward_deployment_enabled, default: true
attribute :separated_caches, default: true
+ validates :merge_trains_skip_train_allowed, inclusion: { in: [true, false] }
chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 92ba02ec777..36f1e09b2ba 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -173,6 +173,10 @@ class ProjectFeature < ApplicationRecord
package_registry_access_level == PUBLIC || project.public?
end
+ def private?(feature)
+ access_level(feature) == PRIVATE
+ end
+
private
def set_pages_access_level
@@ -201,11 +205,11 @@ class ProjectFeature < ApplicationRecord
self.errors.add(field, "cannot have higher visibility level than repository access level") if not_allowed
end
- %i(merge_requests_access_level builds_access_level).each(&validator)
+ %i[merge_requests_access_level builds_access_level].each(&validator)
end
def feature_validation_exclusion
- %i(pages package_registry)
+ %i[pages package_registry]
end
override :resource_member?
diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb
index f16d661d4bb..a7b2c40557a 100644
--- a/app/models/project_import_state.rb
+++ b/app/models/project_import_state.rb
@@ -132,10 +132,17 @@ class ProjectImportState < ApplicationRecord
alias_method :no_import?, :none?
+ # This method is coupled to the repository mirror domain.
+ # Use with caution in the importers domain. As an alternative, use the `#completed?` method.
+ # See EE-override and https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4697
def in_progress?
scheduled? || started?
end
+ def completed?
+ finished? || failed? || canceled?
+ end
+
def started?
# import? does SQL work so only run it if it looks like there's an import running
status == 'started' && project.import?
diff --git a/app/models/project_metrics_setting.rb b/app/models/project_metrics_setting.rb
deleted file mode 100644
index c66d0f52f4c..00000000000
--- a/app/models/project_metrics_setting.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class ProjectMetricsSetting < ApplicationRecord
- belongs_to :project
-
- validates :external_dashboard_url,
- allow_nil: true,
- length: { maximum: 255 },
- addressable_url: { enforce_sanitization: true, ascii_only: true }
-
- enum dashboard_timezone: { local: 0, utc: 1 }
-
- def dashboard_timezone=(val)
- super(val&.downcase)
- end
-end
diff --git a/app/models/project_setting.rb b/app/models/project_setting.rb
index fec951eb7fe..69d1a9f4aeb 100644
--- a/app/models/project_setting.rb
+++ b/app/models/project_setting.rb
@@ -3,28 +3,25 @@
class ProjectSetting < ApplicationRecord
include ::Gitlab::Utils::StrongMemoize
include EachBatch
+ include IgnorableColumns
- ALLOWED_TARGET_PLATFORMS = %w(ios osx tvos watchos android).freeze
+ ALLOWED_TARGET_PLATFORMS = %w[ios osx tvos watchos android].freeze
belongs_to :project, inverse_of: :project_setting
scope :for_projects, ->(projects) { where(project_id: projects) }
- attr_encrypted :cube_api_key,
- mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
- algorithm: 'aes-256-gcm',
- encode: false,
- encode_iv: false
+ ignore_columns %i[
+ encrypted_product_analytics_clickhouse_connection_string
+ encrypted_product_analytics_clickhouse_connection_string_iv
+ encrypted_jitsu_administrator_password
+ encrypted_jitsu_administrator_password_iv
+ jitsu_host
+ jitsu_project_xid
+ jitsu_administrator_email
+ ], remove_with: '16.5', remove_after: '2023-09-22'
- attr_encrypted :jitsu_administrator_password,
- mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_32,
- algorithm: 'aes-256-gcm',
- encode: false,
- encode_iv: false
-
- attr_encrypted :product_analytics_clickhouse_connection_string,
+ attr_encrypted :cube_api_key,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_32,
algorithm: 'aes-256-gcm',
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 3b9b82ee094..34754f4fc95 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -80,6 +80,7 @@ class ProjectTeam
# so we filter out only members of project or project's group
def members_in_project_and_ancestors
members.where(id: member_user_ids)
+ .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/422405')
end
def members_with_access_levels(access_levels = [])
diff --git a/app/models/releases/link.rb b/app/models/releases/link.rb
index 67d765a15c0..e088fe81f6e 100644
--- a/app/models/releases/link.rb
+++ b/app/models/releases/link.rb
@@ -8,10 +8,10 @@ module Releases
# See https://gitlab.com/gitlab-org/gitlab/-/issues/218753
# Regex modified to prevent catastrophic backtracking
- FILEPATH_REGEX = %r{\A\/[^\/](?!.*\/\/.*)[\-\.\w\/]+[\da-zA-Z]+\z}.freeze
+ FILEPATH_REGEX = %r{\A\/[^\/](?!.*\/\/.*)[\-\.\w\/]+[\da-zA-Z]+\z}
FILEPATH_MAX_LENGTH = 128
- validates :url, presence: true, addressable_url: { schemes: %w(http https ftp) }, uniqueness: { scope: :release }
+ validates :url, presence: true, addressable_url: { schemes: %w[http https ftp] }, uniqueness: { scope: :release }
validates :name, presence: true, uniqueness: { scope: :release }
validates :filepath, uniqueness: { scope: :release }, allow_blank: true
validate :filepath_format_valid?
diff --git a/app/models/repository.rb b/app/models/repository.rb
index b8a46f80bc7..1c27a7a64cf 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -47,27 +47,26 @@ class Repository
#
# For example, for entry `:commit_count` there's a method called `commit_count` which
# stores its data in the `commit_count` cache key.
- CACHED_METHODS = %i(size recent_objects_size commit_count readme_path contribution_guide
+ CACHED_METHODS = %i[size recent_objects_size commit_count readme_path contribution_guide
changelog license_blob license_gitaly gitignore
gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? root_ref merged_branch_names
has_visible_content? issue_template_names_hash merge_request_template_names_hash
- user_defined_metrics_dashboard_paths xcode_project? has_ambiguous_refs?).freeze
+ xcode_project? has_ambiguous_refs?].freeze
# Certain method caches should be refreshed when certain types of files are
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
# the corresponding methods to call for refreshing caches.
METHOD_CACHES_FOR_FILE_TYPES = {
- readme: %i(readme_path),
+ readme: %i[readme_path],
changelog: :changelog,
- license: %i(license_blob license_gitaly),
+ license: %i[license_blob license_gitaly],
contributing: :contribution_guide,
gitignore: :gitignore,
gitlab_ci: :gitlab_ci_yml,
avatar: :avatar,
issue_template: :issue_template_names_hash,
merge_request_template: :merge_request_template_names_hash,
- metrics_dashboard: :user_defined_metrics_dashboard_paths,
xcode_config: :xcode_project?
}.freeze
@@ -344,13 +343,13 @@ class Repository
end
def expire_tags_cache
- expire_method_caches(%i(tag_names tag_count has_ambiguous_refs?))
+ expire_method_caches(%i[tag_names tag_count has_ambiguous_refs?])
@tags = nil
@tag_names_include = nil
end
def expire_branches_cache
- expire_method_caches(%i(branch_names merged_branch_names branch_count has_visible_content? has_ambiguous_refs?))
+ expire_method_caches(%i[branch_names merged_branch_names branch_count has_visible_content? has_ambiguous_refs?])
expire_protected_branches_cache
@local_branches = nil
@@ -363,7 +362,7 @@ class Repository
end
def expire_statistics_caches
- expire_method_caches(%i(size recent_objects_size commit_count))
+ expire_method_caches(%i[size recent_objects_size commit_count])
end
def expire_all_method_caches
@@ -371,7 +370,7 @@ class Repository
end
def expire_avatar_cache
- expire_method_caches(%i(avatar))
+ expire_method_caches(%i[avatar])
end
# Refreshes the method caches of this repository.
@@ -412,19 +411,19 @@ class Repository
end
def expire_root_ref_cache
- expire_method_caches(%i(root_ref))
+ expire_method_caches(%i[root_ref])
end
# Expires the cache(s) used to determine if a repository is empty or not.
def expire_emptiness_caches
return unless empty?
- expire_method_caches(%i(has_visible_content?))
+ expire_method_caches(%i[has_visible_content?])
raw_repository.expire_has_local_branches_cache
end
def expire_exists_cache
- expire_method_caches(%i(exists?))
+ expire_method_caches(%i[exists?])
end
# expire cache that doesn't depend on repository data (when expiring)
@@ -628,11 +627,6 @@ class Repository
end
cache_method :merge_request_template_names_hash, fallback: {}
- def user_defined_metrics_dashboard_paths
- Gitlab::Metrics::Dashboard::RepoDashboardFinder.list_dashboards(project)
- end
- cache_method :user_defined_metrics_dashboard_paths, fallback: []
-
def readme
head_tree&.readme
end
@@ -1250,6 +1244,8 @@ class Repository
def get_patch_id(old_revision, new_revision)
raw_repository.get_patch_id(old_revision, new_revision)
+ rescue Gitlab::Git::CommandError
+ nil
end
def object_pool
diff --git a/app/models/resource_label_event.rb b/app/models/resource_label_event.rb
index 13610d37a74..d5c839724d4 100644
--- a/app/models/resource_label_event.rb
+++ b/app/models/resource_label_event.rb
@@ -13,8 +13,7 @@ class ResourceLabelEvent < ResourceEvent
validates :label, presence: { unless: :importing? }, on: :create
validate :exactly_one_issuable, unless: :importing?
- after_destroy :expire_etag_cache
- after_save :expire_etag_cache
+ after_commit :broadcast_notes_changed, unless: :importing?
enum action: {
add: 1,
@@ -22,7 +21,7 @@ class ResourceLabelEvent < ResourceEvent
}
def self.issuable_attrs
- %i(issue merge_request).freeze
+ %i[issue merge_request].freeze
end
def self.preload_label_subjects(events)
@@ -97,8 +96,8 @@ class ResourceLabelEvent < ResourceEvent
issuable.is_a?(MergeRequest) ? :project_merge_requests_url : :project_issues_url
end
- def expire_etag_cache
- issuable.expire_note_etag_cache
+ def broadcast_notes_changed
+ issuable.broadcast_notes_changed
end
def local_label?
diff --git a/app/models/resource_state_event.rb b/app/models/resource_state_event.rb
index 134f71e35ad..88a86258b0a 100644
--- a/app/models/resource_state_event.rb
+++ b/app/models/resource_state_event.rb
@@ -14,7 +14,7 @@ class ResourceStateEvent < ResourceEvent
after_create :issue_usage_metrics
def self.issuable_attrs
- %i(issue merge_request).freeze
+ %i[issue merge_request].freeze
end
def issuable
diff --git a/app/models/resource_timebox_event.rb b/app/models/resource_timebox_event.rb
index 1cc77501d8d..644ffae5749 100644
--- a/app/models/resource_timebox_event.rb
+++ b/app/models/resource_timebox_event.rb
@@ -16,7 +16,7 @@ class ResourceTimeboxEvent < ResourceEvent
after_create :issue_usage_metrics
def self.issuable_attrs
- %i(issue merge_request).freeze
+ %i[issue merge_request].freeze
end
def issuable
diff --git a/app/models/review.rb b/app/models/review.rb
index d47aaf027ce..98e9a314df7 100644
--- a/app/models/review.rb
+++ b/app/models/review.rb
@@ -31,6 +31,10 @@ class Review < ApplicationRecord
def user_mentions
merge_request.user_mentions.where.not(note_id: nil)
end
+
+ def from_merge_request_author?
+ merge_request.author_id == author_id
+ end
end
Review.prepend_mod
diff --git a/app/models/self_managed_prometheus_alert_event.rb b/app/models/self_managed_prometheus_alert_event.rb
deleted file mode 100644
index cf26563e92d..00000000000
--- a/app/models/self_managed_prometheus_alert_event.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-class SelfManagedPrometheusAlertEvent < ApplicationRecord
- include AlertEventLifecycle
-
- belongs_to :project, validate: true, inverse_of: :self_managed_prometheus_alert_events
- belongs_to :environment, validate: true, inverse_of: :self_managed_prometheus_alert_events
- has_and_belongs_to_many :related_issues, class_name: 'Issue', join_table: :issues_self_managed_prometheus_alert_events # rubocop:disable Rails/HasAndBelongsToMany
-
- validates :started_at, presence: true
- validates :payload_key, uniqueness: { scope: :project_id }
-
- def self.find_or_initialize_by_payload_key(project, payload_key)
- find_or_initialize_by(project: project, payload_key: payload_key) do |event|
- yield event if block_given?
- end
- end
-end
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index f3a0479d3b7..30c53b978f8 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class SentNotification < ApplicationRecord
+ include IgnorableColumns
+
+ ignore_column %i[id_convert_to_bigint], remove_with: '16.5', remove_after: '2023-09-22'
+
belongs_to :project
belongs_to :noteable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
belongs_to :recipient, class_name: "User"
diff --git a/app/models/snippet_repository.rb b/app/models/snippet_repository.rb
index 9139dc22a94..a262802c8af 100644
--- a/app/models/snippet_repository.rb
+++ b/app/models/snippet_repository.rb
@@ -5,7 +5,7 @@ class SnippetRepository < ApplicationRecord
include Shardable
DEFAULT_EMPTY_FILE_NAME = 'snippetfile'
- EMPTY_FILE_PATTERN = /^#{DEFAULT_EMPTY_FILE_NAME}(\d+)\.txt$/.freeze
+ EMPTY_FILE_PATTERN = /^#{DEFAULT_EMPTY_FILE_NAME}(\d+)\.txt$/
CommitError = Class.new(StandardError)
InvalidPathError = Class.new(CommitError)
diff --git a/app/models/terraform/state.rb b/app/models/terraform/state.rb
index ecd3e27a9c4..7caf3a1040b 100644
--- a/app/models/terraform/state.rb
+++ b/app/models/terraform/state.rb
@@ -5,7 +5,7 @@ module Terraform
include UsageStatistics
include AfterCommitQueue
- HEX_REGEXP = %r{\A\h+\z}.freeze
+ HEX_REGEXP = %r{\A\h+\z}
UUID_LENGTH = 32
self.locking_column = :activerecord_lock_version
diff --git a/app/models/user.rb b/app/models/user.rb
index 9f85d41b133..c4e867ab571 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -22,7 +22,6 @@ class User < MainClusterwide::ApplicationRecord
include FromUnion
include BatchDestroyDependentAssociations
include BatchNullifyDependentAssociations
- include HasUniqueInternalUsers
include IgnorableColumns
include UpdateHighestRole
include HasUserType
@@ -31,7 +30,28 @@ class User < MainClusterwide::ApplicationRecord
include RestrictedSignup
include StripAttribute
include EachBatch
- include SafelyChangeColumnDefault
+ include CrossDatabaseIgnoredTables
+ include IgnorableColumns
+
+ ignore_column %i[
+ email_opted_in
+ email_opted_in_ip
+ email_opted_in_source_id
+ email_opted_in_at
+ ], remove_with: '16.6', remove_after: '2023-10-22'
+
+ # `ensure_namespace_correct` needs to be moved to an after_commit (?)
+ cross_database_ignore_tables %w[namespaces namespace_settings], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424279'
+
+ # `notification_settings_for` is called, and elsewhere `save` is then called.
+ cross_database_ignore_tables %w[notification_settings], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424284'
+
+ # Associations with dependent: option
+ cross_database_ignore_tables(
+ %w[namespaces projects project_authorizations issues merge_requests merge_requests issues issues merge_requests],
+ url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424285',
+ on: :destroy
+ )
DEFAULT_NOTIFICATION_LEVEL = :participating
@@ -55,13 +75,11 @@ class User < MainClusterwide::ApplicationRecord
:public_email
].freeze
- FORBIDDEN_SEARCH_STATES = %w(blocked banned ldap_blocked).freeze
+ FORBIDDEN_SEARCH_STATES = %w[blocked banned ldap_blocked].freeze
INCOMING_MAIL_TOKEN_PREFIX = 'glimt-'
FEED_TOKEN_PREFIX = 'glft-'
- columns_changing_default :project_view
-
# lib/tasks/tokens.rake needs to be updated when changing mail and feed tokens
add_authentication_token_field :incoming_email_token, token_generator: -> { self.generate_incoming_mail_token }
add_authentication_token_field :feed_token, format_with_prefix: :prefix_for_feed_token
@@ -262,8 +280,6 @@ class User < MainClusterwide::ApplicationRecord
has_many :organization_users, class_name: 'Organizations::OrganizationUser', inverse_of: :user
has_many :organizations, through: :organization_users, class_name: 'Organizations::Organization', inverse_of: :users
- has_many :metrics_users_starred_dashboards, class_name: 'Metrics::UsersStarredDashboard', inverse_of: :user
-
has_one :status, class_name: 'UserStatus'
has_one :user_preference
has_one :user_detail
@@ -346,7 +362,9 @@ class User < MainClusterwide::ApplicationRecord
email_to_confirm.confirm
end
else
- add_primary_email_to_emails!
+ ignore_cross_database_tables_if_factory_bot(%w[emails]) do
+ add_primary_email_to_emails!
+ end
end
end
after_commit(on: :update) do
@@ -378,6 +396,7 @@ class User < MainClusterwide::ApplicationRecord
:gitpod_enabled, :gitpod_enabled=,
:setup_for_company, :setup_for_company=,
:project_shortcut_buttons, :project_shortcut_buttons=,
+ :keyboard_shortcuts_enabled, :keyboard_shortcuts_enabled=,
:render_whitespace_in_code, :render_whitespace_in_code=,
:markdown_surround_selection, :markdown_surround_selection=,
:markdown_automatic_lists, :markdown_automatic_lists=,
@@ -501,11 +520,19 @@ class User < MainClusterwide::ApplicationRecord
end
after_transition any => :active do |user|
- user.starred_projects.update_counters(star_count: 1)
+ user.class.temporary_ignore_cross_database_tables(
+ %w[projects], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424278'
+ ) do
+ user.starred_projects.update_counters(star_count: 1)
+ end
end
after_transition active: any do |user|
- user.starred_projects.update_counters(star_count: -1)
+ user.class.temporary_ignore_cross_database_tables(
+ %w[projects], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424278'
+ ) do
+ user.starred_projects.update_counters(star_count: -1)
+ end
end
end
@@ -884,92 +911,6 @@ class User < MainClusterwide::ApplicationRecord
}x
end
- # Return (create if necessary) the ghost user. The ghost user
- # owns records previously belonging to deleted users.
- def ghost
- email = 'ghost%s@example.com'
- unique_internal(where(user_type: :ghost), 'ghost', email) do |u|
- u.bio = _('This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.')
- u.name = 'Ghost User'
- end
- end
-
- def alert_bot
- email_pattern = "alert%s@#{Settings.gitlab.host}"
-
- unique_internal(where(user_type: :alert_bot), 'alert-bot', email_pattern) do |u|
- u.bio = 'The GitLab alert bot'
- u.name = 'GitLab Alert Bot'
- u.avatar = bot_avatar(image: 'alert-bot.png')
- end
- end
-
- def migration_bot
- email_pattern = "noreply+gitlab-migration-bot%s@#{Settings.gitlab.host}"
-
- unique_internal(where(user_type: :migration_bot), 'migration-bot', email_pattern) do |u|
- u.bio = 'The GitLab migration bot'
- u.name = 'GitLab Migration Bot'
- u.confirmed_at = Time.zone.now
- end
- end
-
- def security_bot
- email_pattern = "security-bot%s@#{Settings.gitlab.host}"
-
- unique_internal(where(user_type: :security_bot), 'GitLab-Security-Bot', email_pattern) do |u|
- u.bio = 'System bot that monitors detected vulnerabilities for solutions and creates merge requests with the fixes.'
- u.name = 'GitLab Security Bot'
- u.website_url = Gitlab::Routing.url_helpers.help_page_url('user/application_security/security_bot/index.md')
- u.avatar = bot_avatar(image: 'security-bot.png')
- u.confirmed_at = Time.zone.now
- end
- end
-
- def support_bot
- email_pattern = "support%s@#{Settings.gitlab.host}"
-
- unique_internal(where(user_type: :support_bot), 'support-bot', email_pattern) do |u|
- u.bio = 'The GitLab support bot used for Service Desk'
- u.name = 'GitLab Support Bot'
- u.avatar = bot_avatar(image: 'support-bot.png')
- u.confirmed_at = Time.zone.now
- end
- end
-
- def automation_bot
- email_pattern = "automation%s@#{Settings.gitlab.host}"
-
- unique_internal(where(user_type: :automation_bot), 'automation-bot', email_pattern) do |u|
- u.bio = 'The GitLab automation bot used for automated workflows and tasks'
- u.name = 'GitLab Automation Bot'
- u.avatar = bot_avatar(image: 'support-bot.png') # todo: add an avatar for automation-bot
- end
- end
-
- def llm_bot
- email_pattern = "llm-bot%s@#{Settings.gitlab.host}"
-
- unique_internal(where(user_type: :llm_bot), 'GitLab-Llm-Bot', email_pattern) do |u|
- u.bio = 'The Gitlab LLM bot used for fetching LLM-generated content'
- u.name = 'GitLab LLM Bot'
- u.avatar = bot_avatar(image: 'support-bot.png') # todo: add an avatar for llm-bot
- u.confirmed_at = Time.zone.now
- end
- end
-
- def admin_bot
- email_pattern = "admin-bot%s@#{Settings.gitlab.host}"
-
- unique_internal(where(user_type: :admin_bot), 'GitLab-Admin-Bot', email_pattern) do |u|
- u.bio = 'Admin bot used for tasks that require admin privileges'
- u.name = 'GitLab Admin Bot'
- u.avatar = bot_avatar(image: 'admin-bot.png')
- u.admin = true
- u.confirmed_at = Time.zone.now
- end
- end
-
# Return true if there is only single non-internal user in the deployment,
# ghost user is ignored.
def single_user?
@@ -2009,7 +1950,7 @@ class User < MainClusterwide::ApplicationRecord
def access_level=(new_level)
new_level = new_level.to_s
- return unless %w(admin regular).include?(new_level)
+ return unless %w[admin regular].include?(new_level)
self.admin = (new_level == 'admin')
end
@@ -2175,16 +2116,6 @@ class User < MainClusterwide::ApplicationRecord
[last_activity, last_sign_in].compact.max
end
- REQUIRES_ROLE_VALUE = 99
-
- def role_required?
- role_before_type_cast == REQUIRES_ROLE_VALUE
- end
-
- def set_role_required!
- update_column(:role, REQUIRES_ROLE_VALUE)
- end
-
def dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil)
callout = callouts_by_feature_name[feature_name]
@@ -2354,7 +2285,7 @@ class User < MainClusterwide::ApplicationRecord
def ban_and_report
msg = 'Potential spammer account deletion'
- attrs = { user_id: id, reporter: User.security_bot, category: 'spam' }
+ attrs = { user_id: id, reporter: Users::Internal.security_bot, category: 'spam' }
abuse_report = AbuseReport.find_by(attrs)
if abuse_report.nil?
@@ -2519,7 +2450,7 @@ class User < MainClusterwide::ApplicationRecord
def update_highest_role?
return false unless persisted?
- (previous_changes.keys & %w(state user_type)).any?
+ (previous_changes.keys & %w[state user_type]).any?
end
def update_highest_role_attribute
diff --git a/app/models/user_custom_attribute.rb b/app/models/user_custom_attribute.rb
index 425f2cc062b..15d50071bf6 100644
--- a/app/models/user_custom_attribute.rb
+++ b/app/models/user_custom_attribute.rb
@@ -15,6 +15,7 @@ class UserCustomAttribute < ApplicationRecord
UNBLOCKED_BY = 'unblocked_by'
ARKOSE_RISK_BAND = 'arkose_risk_band'
AUTO_BANNED_BY_ABUSE_REPORT_ID = 'auto_banned_by_abuse_report_id'
+ AUTO_BANNED_BY_SPAM_LOG_ID = 'auto_banned_by_spam_log_id'
ALLOW_POSSIBLE_SPAM = 'allow_possible_spam'
IDENTITY_VERIFICATION_PHONE_EXEMPT = 'identity_verification_phone_exempt'
@@ -45,6 +46,14 @@ class UserCustomAttribute < ApplicationRecord
upsert_custom_attributes([custom_attribute])
end
+ def set_banned_by_spam_log(spam_log)
+ return unless spam_log
+
+ custom_attribute = { user_id: spam_log.user_id, key: AUTO_BANNED_BY_SPAM_LOG_ID, value: spam_log.id }
+
+ upsert_custom_attributes([custom_attribute])
+ end
+
private
def blocked_users
diff --git a/app/models/user_interacted_project.rb b/app/models/user_interacted_project.rb
index 1c7515894fe..73bca362960 100644
--- a/app/models/user_interacted_project.rb
+++ b/app/models/user_interacted_project.rb
@@ -24,7 +24,7 @@ class UserInteractedProject < ApplicationRecord
}
cached_exists?(**attributes) do
- where(attributes).exists? || UserInteractedProject.insert_all([attributes], unique_by: %w(project_id user_id))
+ where(attributes).exists? || UserInteractedProject.insert_all([attributes], unique_by: %w[project_id user_id])
true
end
end
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index eac66905d0c..8fc9f4617d0 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -35,6 +35,7 @@ class UserPreference < MainClusterwide::ApplicationRecord
attribute :time_display_relative, default: true
attribute :render_whitespace_in_code, default: false
attribute :project_shortcut_buttons, default: true
+ attribute :keyboard_shortcuts_enabled, default: true
enum visibility_pipeline_id_type: { id: 0, iid: 1 }
diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb
index 0d3262b2474..def0765560e 100644
--- a/app/models/users/callout.rb
+++ b/app/models/users/callout.rb
@@ -62,7 +62,7 @@ module Users
project_quality_summary_feedback: 59, # EE-only
merge_request_settings_moved_callout: 60,
new_top_level_group_alert: 61,
- artifacts_management_page_feedback_banner: 62,
+ # 62, removed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131314
# 63 and 64 were removed with https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120233
branch_rules_info_callout: 65,
create_runner_workflow_banner: 66,
@@ -71,9 +71,11 @@ module Users
project_repository_limit_alert_alert_threshold: 69, # EE-only
project_repository_limit_alert_error_threshold: 70, # EE-only
new_navigation_callout: 71,
- code_suggestions_third_party_callout: 72, # EE-only
+ # 72 removed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129022
namespace_over_storage_users_combined_alert: 73, # EE-only
- rich_text_editor: 74
+ rich_text_editor: 74,
+ vsd_feedback_banner: 75, # EE-only
+ security_policy_protected_branch_modification: 76 # EE-only
}
validates :feature_name,
diff --git a/app/models/users/credit_card_validation.rb b/app/models/users/credit_card_validation.rb
index 1b0fd8682db..086943884a5 100644
--- a/app/models/users/credit_card_validation.rb
+++ b/app/models/users/credit_card_validation.rb
@@ -16,6 +16,12 @@ module Users
greater_than_or_equal_to: 0, less_than_or_equal_to: 9999
}
+ validates :last_digits_hash, length: { maximum: 44 }
+ validates :holder_name_hash, length: { maximum: 44 }
+ validates :expiration_date_hash, length: { maximum: 44 }
+ validates :network_hash, length: { maximum: 44 }
+
+ scope :find_or_initialize_by_user, ->(user_id) { where(user_id: user_id).first_or_initialize }
scope :by_banned_user, -> { joins(:banned_user) }
scope :similar_by_holder_name, ->(holder_name) do
if holder_name.present?
@@ -32,6 +38,11 @@ module Users
)
end
+ before_save :set_last_digits_hash, if: -> { last_digits.present? }
+ before_save :set_holder_name_hash, if: -> { holder_name.present? }
+ before_save :set_network_hash, if: -> { network.present? }
+ before_save :set_expiration_date_hash, if: -> { expiration_date.present? }
+
def similar_records
self.class.similar_to(self).order(credit_card_validated_at: :desc).includes(:user)
end
@@ -43,5 +54,21 @@ module Users
def used_by_banned_user?
self.class.by_banned_user.similar_to(self).similar_by_holder_name(holder_name).exists?
end
+
+ def set_last_digits_hash
+ self.last_digits_hash = Gitlab::CryptoHelper.sha256(last_digits)
+ end
+
+ def set_holder_name_hash
+ self.holder_name_hash = Gitlab::CryptoHelper.sha256(holder_name.downcase)
+ end
+
+ def set_network_hash
+ self.network_hash = Gitlab::CryptoHelper.sha256(network.downcase)
+ end
+
+ def set_expiration_date_hash
+ self.expiration_date_hash = Gitlab::CryptoHelper.sha256(expiration_date.to_s)
+ end
end
end
diff --git a/app/models/users/group_visit.rb b/app/models/users/group_visit.rb
new file mode 100644
index 00000000000..0bcfda049fc
--- /dev/null
+++ b/app/models/users/group_visit.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Users
+ class GroupVisit < ApplicationRecord
+ include Users::Visitable
+ include PartitionedTable
+
+ self.table_name = "groups_visits"
+ self.primary_key = :id
+
+ partitioned_by :visited_at, strategy: :monthly, retain_for: 3.months
+
+ validates :entity_id, presence: true
+ validates :user_id, presence: true
+ validates :visited_at, presence: true
+ end
+end
diff --git a/app/models/users/project_callout.rb b/app/models/users/project_callout.rb
index 3964f202be6..6affe5b5030 100644
--- a/app/models/users/project_callout.rb
+++ b/app/models/users/project_callout.rb
@@ -11,10 +11,10 @@ module Users
enum feature_name: {
awaiting_members_banner: 1, # EE-only
web_hook_disabled: 2,
- ultimate_feature_removal_banner: 3,
+ # 3 was removed https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129703,
+ # and cleaned up https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129924, it can be replaced
namespace_storage_pre_enforcement_banner: 4, # EE-only
- # 5,6,7 were unused and removed with https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118330,
- # they can be replaced.
+ # 5,6,7 were removed https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118330, they can be replaced
license_check_deprecation_alert: 8 # EE-only
}
diff --git a/app/models/users/project_visit.rb b/app/models/users/project_visit.rb
new file mode 100644
index 00000000000..1d076e0be56
--- /dev/null
+++ b/app/models/users/project_visit.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Users
+ class ProjectVisit < ApplicationRecord
+ include Users::Visitable
+ include PartitionedTable
+
+ self.table_name = "projects_visits"
+ self.primary_key = :id
+
+ partitioned_by :visited_at, strategy: :monthly, retain_for: 3.months
+
+ validates :entity_id, presence: true
+ validates :user_id, presence: true
+ validates :visited_at, presence: true
+ end
+end
diff --git a/app/models/work_item.rb b/app/models/work_item.rb
index 73156b2f040..62b837eeeb6 100644
--- a/app/models/work_item.rb
+++ b/app/models/work_item.rb
@@ -4,7 +4,8 @@ class WorkItem < Issue
include Gitlab::Utils::StrongMemoize
COMMON_QUICK_ACTIONS_COMMANDS = [
- :title, :reopen, :close, :cc, :tableflip, :shrug, :type, :promote_to
+ :title, :reopen, :close, :cc, :tableflip, :shrug, :type, :promote_to, :checkin_reminder,
+ :subscribe, :unsubscribe, :confidential, :award
].freeze
self.table_name = 'issues'
@@ -146,6 +147,18 @@ class WorkItem < Issue
{ common: common_params, widgets: widget_params }
end
+ def linked_work_items(current_user = nil, authorize: true, preload: nil, link_type: nil)
+ linked_work_items = linked_work_items_query(link_type).preload(preload).reorder('issue_link_id')
+ return linked_work_items unless authorize
+
+ cross_project_filter = ->(work_items) { work_items.where(project: project) }
+ Ability.work_items_readable_by_user(
+ linked_work_items,
+ current_user,
+ filters: { read_cross_project: cross_project_filter }
+ )
+ end
+
private
override :parent_link_confidentiality
@@ -241,6 +254,21 @@ class WorkItem < Issue
errors.add(:work_item_type_id, _('reached maximum depth'))
end
end
+
+ def linked_work_items_query(link_type)
+ type_condition =
+ if link_type == WorkItems::RelatedWorkItemLink::TYPE_RELATES_TO
+ " AND issue_links.link_type = #{WorkItems::RelatedWorkItemLink.link_types[link_type]}"
+ else
+ ""
+ end
+
+ linked_issues_select
+ .joins("INNER JOIN issue_links ON
+ (issue_links.source_id = issues.id AND issue_links.target_id = #{id}#{type_condition})
+ OR
+ (issue_links.target_id = issues.id AND issue_links.source_id = #{id}#{type_condition})")
+ end
end
WorkItem.prepend_mod
diff --git a/app/models/work_items/parent_link.rb b/app/models/work_items/parent_link.rb
index d9e3690b6fc..ea7755b03b4 100644
--- a/app/models/work_items/parent_link.rb
+++ b/app/models/work_items/parent_link.rb
@@ -7,7 +7,6 @@ module WorkItems
self.table_name = 'work_item_parent_links'
MAX_CHILDREN = 100
- PARENT_TYPES = [:issue, :incident].freeze
belongs_to :work_item
belongs_to :work_item_parent, class_name: 'WorkItem'
@@ -122,3 +121,5 @@ module WorkItems
end
end
end
+
+WorkItems::ParentLink.prepend_mod
diff --git a/app/models/work_items/related_work_item_link.rb b/app/models/work_items/related_work_item_link.rb
index 4de197d3d35..a911ef5f05d 100644
--- a/app/models/work_items/related_work_item_link.rb
+++ b/app/models/work_items/related_work_item_link.rb
@@ -6,9 +6,13 @@ module WorkItems
self.table_name = 'issue_links'
+ MAX_LINKS_COUNT = 100
+
belongs_to :source, class_name: 'WorkItem'
belongs_to :target, class_name: 'WorkItem'
+ validate :validate_max_number_of_links, on: :create
+
class << self
extend ::Gitlab::Utils::Override
@@ -23,5 +27,15 @@ module WorkItems
'work item'
end
end
+
+ def validate_max_number_of_links
+ if source && source.linked_work_items(authorize: false).size >= MAX_LINKS_COUNT
+ errors.add :source, s_('WorkItems|This work item would exceed the maximum number of linked items.')
+ end
+
+ return unless target && target.linked_work_items(authorize: false).size >= MAX_LINKS_COUNT
+
+ errors.add :target, s_('WorkItems|This work item would exceed the maximum number of linked items.')
+ end
end
end
diff --git a/app/models/work_items/type.rb b/app/models/work_items/type.rb
index 369ffc660aa..b7ceeecbc7f 100644
--- a/app/models/work_items/type.rb
+++ b/app/models/work_items/type.rb
@@ -44,8 +44,6 @@ module WorkItems
# where it's possible to switch between issue and incident.
CHANGEABLE_BASE_TYPES = %w[issue incident test_case].freeze
- WI_TYPES_WITH_CREATED_HEADER = %w[issue incident ticket].freeze
-
cache_markdown_field :description, pipeline: :single_line
enum base_type: BASE_TYPES.transform_values { |value| value[:enum_value] }
diff --git a/app/models/work_items/widgets/linked_items.rb b/app/models/work_items/widgets/linked_items.rb
index 06a0f6db964..b405555c038 100644
--- a/app/models/work_items/widgets/linked_items.rb
+++ b/app/models/work_items/widgets/linked_items.rb
@@ -3,7 +3,7 @@
module WorkItems
module Widgets
class LinkedItems < Base
- delegate :related_issues, to: :work_item
+ delegate :linked_work_items, to: :work_item
end
end
end
diff --git a/app/models/x509_certificate.rb b/app/models/x509_certificate.rb
index 7c2581b8bb2..90f3bd69c47 100644
--- a/app/models/x509_certificate.rb
+++ b/app/models/x509_certificate.rb
@@ -17,8 +17,6 @@ class X509Certificate < ApplicationRecord
# rfc 5280 - 4.2.1.2 Subject Key Identifier
validates :subject_key_identifier, presence: true, format: { with: Gitlab::Regex.x509_subject_key_identifier_regex }
- # rfc 5280 - 4.1.2.6 Subject
- validates :subject, presence: true
# rfc 5280 - 4.1.2.6 Subject (subjectAltName contains the email address)
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
# rfc 5280 - 4.1.2.2 Serial number
diff --git a/app/models/x509_issuer.rb b/app/models/x509_issuer.rb
index 81491d8e507..769d56a9838 100644
--- a/app/models/x509_issuer.rb
+++ b/app/models/x509_issuer.rb
@@ -6,13 +6,16 @@ class X509Issuer < ApplicationRecord
# rfc 5280 - 4.2.1.1 Authority Key Identifier
validates :subject_key_identifier, presence: true, format: { with: Gitlab::Regex.x509_subject_key_identifier_regex }
# rfc 5280 - 4.1.2.4 Issuer
- validates :subject, presence: true
# rfc 5280 - 4.2.1.13 CRL Distribution Points
# cRLDistributionPoints extension using URI:http
- validates :crl_url, presence: true, public_url: true
+ validates :crl_url, allow_nil: true, public_url: true
def self.safe_create!(attributes)
create_with(attributes)
.safe_find_or_create_by!(subject_key_identifier: attributes[:subject_key_identifier])
end
+
+ def self.with_crl_url
+ where.not(crl_url: nil)
+ end
end