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