diff options
Diffstat (limited to 'app/models')
48 files changed, 585 insertions, 255 deletions
diff --git a/app/models/chat_team.rb b/app/models/chat_team.rb index 52b5a7b4a91..28aab279545 100644 --- a/app/models/chat_team.rb +++ b/app/models/chat_team.rb @@ -12,6 +12,6 @@ class ChatTeam < ApplicationRecord # Either the group is not found, or the user doesn't have the proper # access on the mattermost instance. In the first case, we're done either way # in the latter case, we can't recover by retrying, so we just log what happened - Rails.logger.error("Mattermost team deletion failed: #{e}") + Rails.logger.error("Mattermost team deletion failed: #{e}") # rubocop:disable Gitlab/RailsLogger end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 89cc082d0bc..635fcc86166 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -266,7 +266,7 @@ module Ci begin Ci::Build.retry(build, build.user) rescue Gitlab::Access::AccessDeniedError => ex - Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}" + Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}" # rubocop:disable Gitlab/RailsLogger end end end @@ -578,7 +578,7 @@ module Ci end def valid_token?(token) - self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) + self.token && ActiveSupport::SecurityUtils.secure_compare(token, self.token) end def has_tags? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 20ca4a9ab24..2262282e647 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -196,7 +196,7 @@ module Ci sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC' query = ApplicationRecord.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend - order(query) + order(Arel.sql(query)) end scope :for_user, -> (user) { where(user: user) } diff --git a/app/models/clusters/clusters_hierarchy.rb b/app/models/clusters/clusters_hierarchy.rb new file mode 100644 index 00000000000..dab034b7234 --- /dev/null +++ b/app/models/clusters/clusters_hierarchy.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +module Clusters + class ClustersHierarchy + DEPTH_COLUMN = :depth + + def initialize(clusterable) + @clusterable = clusterable + end + + # Returns clusters in order from deepest to highest group + def base_and_ancestors + cte = recursive_cte + cte_alias = cte.table.alias(model.table_name) + + model + .unscoped + .where('clusters.id IS NOT NULL') + .with + .recursive(cte.to_arel) + .from(cte_alias) + .order(DEPTH_COLUMN => :asc) + end + + private + + attr_reader :clusterable + + def recursive_cte + cte = Gitlab::SQL::RecursiveCTE.new(:clusters_cte) + + base_query = case clusterable + when ::Group + group_clusters_base_query + when ::Project + project_clusters_base_query + else + raise ArgumentError, "unknown type for #{clusterable}" + end + + cte << base_query + cte << parent_query(cte) + + cte + end + + def group_clusters_base_query + group_parent_id_alias = alias_as_column(groups[:parent_id], 'group_parent_id') + join_sources = ::Group.left_joins(:clusters).join_sources + + model + .unscoped + .select([clusters_star, group_parent_id_alias, "1 AS #{DEPTH_COLUMN}"]) + .where(groups[:id].eq(clusterable.id)) + .from(groups) + .joins(join_sources) + end + + def project_clusters_base_query + projects = ::Project.arel_table + project_parent_id_alias = alias_as_column(projects[:namespace_id], 'group_parent_id') + join_sources = ::Project.left_joins(:clusters).join_sources + + model + .unscoped + .select([clusters_star, project_parent_id_alias, "1 AS #{DEPTH_COLUMN}"]) + .where(projects[:id].eq(clusterable.id)) + .from(projects) + .joins(join_sources) + end + + def parent_query(cte) + group_parent_id_alias = alias_as_column(groups[:parent_id], 'group_parent_id') + + model + .unscoped + .select([clusters_star, group_parent_id_alias, cte.table[DEPTH_COLUMN] + 1]) + .from([cte.table, groups]) + .joins('LEFT OUTER JOIN cluster_groups ON cluster_groups.group_id = namespaces.id') + .joins('LEFT OUTER JOIN clusters ON cluster_groups.cluster_id = clusters.id') + .where(groups[:id].eq(cte.table[:group_parent_id])) + end + + def model + Clusters::Cluster + end + + def clusters + @clusters ||= model.arel_table + end + + def groups + @groups ||= ::Group.arel_table + end + + def clusters_star + @clusters_star ||= clusters[Arel.star] + end + + def alias_as_column(value, alias_to) + Arel::Nodes::As.new(value, Arel::Nodes::SqlLiteral.new(alias_to)) + end + end +end diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index 42203a5f214..9713e79f525 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -87,6 +87,16 @@ module CacheMarkdownField __send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend end + # Updates the markdown cache if necessary, then returns the field + # Unlike `cached_html_for` it returns `nil` if the field does not exist + def updated_cached_html_for(markdown_field) + return unless cached_markdown_fields.markdown_fields.include?(markdown_field) + + refresh_markdown_cache if attribute_invalidated?(cached_markdown_fields.html_field(markdown_field)) + + cached_html_for(markdown_field) + end + def latest_cached_markdown_version @latest_cached_markdown_version ||= (Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) | local_version end @@ -139,8 +149,9 @@ module CacheMarkdownField # The HTML becomes invalid if any dependent fields change. For now, assume # author and project invalidate the cache in all circumstances. define_method(invalidation_method) do - invalidations = changed_markdown_fields & [markdown_field.to_s, *INVALIDATED_BY] - invalidations.delete(markdown_field.to_s) if changed_markdown_fields.include?("#{markdown_field}_html") + changed_fields = changed_attributes.keys + invalidations = changed_fields & [markdown_field.to_s, *INVALIDATED_BY] + invalidations.delete(markdown_field.to_s) if changed_fields.include?("#{markdown_field}_html") !invalidations.empty? || !cached_html_up_to_date?(markdown_field) end end diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb index 8cbf4bcfaf7..53dff2adfc3 100644 --- a/app/models/concerns/cacheable_attributes.rb +++ b/app/models/concerns/cacheable_attributes.rb @@ -49,7 +49,7 @@ module CacheableAttributes current_without_cache.tap { |current_record| current_record&.cache! } rescue => e if Rails.env.production? - Rails.logger.warn("Cached record for #{name} couldn't be loaded, falling back to uncached record: #{e}") + Rails.logger.warn("Cached record for #{name} couldn't be loaded, falling back to uncached record: #{e}") # rubocop:disable Gitlab/RailsLogger else raise e end diff --git a/app/models/concerns/ci/contextable.rb b/app/models/concerns/ci/contextable.rb index e1d5ce7f7d4..91dda803031 100644 --- a/app/models/concerns/ci/contextable.rb +++ b/app/models/concerns/ci/contextable.rb @@ -59,6 +59,7 @@ module Ci variables.append(key: 'CI', value: 'true') variables.append(key: 'GITLAB_CI', value: 'true') variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(',')) + variables.append(key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host) variables.append(key: 'CI_SERVER_NAME', value: 'GitLab') variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION) variables.append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s) diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb index 5a358ae2ef6..8f28c897eb6 100644 --- a/app/models/concerns/deployment_platform.rb +++ b/app/models/concerns/deployment_platform.rb @@ -12,11 +12,26 @@ module DeploymentPlatform private def find_deployment_platform(environment) - find_cluster_platform_kubernetes(environment: environment) || - find_group_cluster_platform_kubernetes(environment: environment) || + find_platform_kubernetes(environment) || find_instance_cluster_platform_kubernetes(environment: environment) end + def find_platform_kubernetes(environment) + if Feature.enabled?(:clusters_cte) + find_platform_kubernetes_with_cte(environment) + else + find_cluster_platform_kubernetes(environment: environment) || + find_group_cluster_platform_kubernetes(environment: environment) + end + end + + # EE would override this and utilize environment argument + def find_platform_kubernetes_with_cte(_environment) + Clusters::ClustersHierarchy.new(self).base_and_ancestors + .enabled.default_environment + .first&.platform_kubernetes + end + # EE would override this and utilize environment argument def find_cluster_platform_kubernetes(environment: nil) clusters.enabled.default_environment diff --git a/app/models/concerns/from_union.rb b/app/models/concerns/from_union.rb index 9b8595b1211..e28dee34815 100644 --- a/app/models/concerns/from_union.rb +++ b/app/models/concerns/from_union.rb @@ -40,11 +40,7 @@ module FromUnion .new(members, remove_duplicates: remove_duplicates) .to_sql - # This pattern is necessary as a bug in Rails 4 can cause the use of - # `from("string here").includes(:foo)` to break ActiveRecord. This is - # fixed in https://github.com/rails/rails/pull/25374, which is released as - # part of Rails 5. - from([Arel.sql("(#{union}) #{alias_as}")]) + from(Arel.sql("(#{union}) #{alias_as}")) end # rubocop: enable Gitlab/Union end diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 78bcce2f592..27a5c3d5286 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -33,22 +33,24 @@ module HasStatus canceled = scope_relevant.canceled.select('count(*)').to_sql warnings = scope_warnings.select('count(*) > 0').to_sql.presence || 'false' - "(CASE - WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success' - WHEN (#{builds})=(#{skipped}) THEN 'skipped' - WHEN (#{builds})=(#{success}) THEN 'success' - WHEN (#{builds})=(#{created}) THEN 'created' - WHEN (#{builds})=(#{preparing}) THEN 'preparing' - WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success' - WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' - WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' - WHEN (#{running})+(#{pending})>0 THEN 'running' - WHEN (#{manual})>0 THEN 'manual' - WHEN (#{scheduled})>0 THEN 'scheduled' - WHEN (#{preparing})>0 THEN 'preparing' - WHEN (#{created})>0 THEN 'running' - ELSE 'failed' - END)" + Arel.sql( + "(CASE + WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success' + WHEN (#{builds})=(#{skipped}) THEN 'skipped' + WHEN (#{builds})=(#{success}) THEN 'success' + WHEN (#{builds})=(#{created}) THEN 'created' + WHEN (#{builds})=(#{preparing}) THEN 'preparing' + WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success' + WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' + WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' + WHEN (#{running})+(#{pending})>0 THEN 'running' + WHEN (#{manual})>0 THEN 'manual' + WHEN (#{scheduled})>0 THEN 'scheduled' + WHEN (#{preparing})>0 THEN 'preparing' + WHEN (#{created})>0 THEN 'running' + ELSE 'failed' + END)" + ) end def status @@ -88,22 +90,22 @@ module HasStatus state :scheduled, value: 'scheduled' end - scope :created, -> { where(status: 'created') } - scope :preparing, -> { where(status: 'preparing') } - scope :relevant, -> { where(status: AVAILABLE_STATUSES - ['created']) } - scope :running, -> { where(status: 'running') } - scope :pending, -> { where(status: 'pending') } - scope :success, -> { where(status: 'success') } - scope :failed, -> { where(status: 'failed') } - scope :canceled, -> { where(status: 'canceled') } - scope :skipped, -> { where(status: 'skipped') } - scope :manual, -> { where(status: 'manual') } - scope :scheduled, -> { where(status: 'scheduled') } - scope :alive, -> { where(status: [:created, :preparing, :pending, :running]) } - scope :created_or_pending, -> { where(status: [:created, :pending]) } - scope :running_or_pending, -> { where(status: [:running, :pending]) } - scope :finished, -> { where(status: [:success, :failed, :canceled]) } - scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) } + scope :created, -> { with_status(:created) } + scope :preparing, -> { with_status(:preparing) } + scope :relevant, -> { without_status(:created) } + scope :running, -> { with_status(:running) } + scope :pending, -> { with_status(:pending) } + scope :success, -> { with_status(:success) } + scope :failed, -> { with_status(:failed) } + scope :canceled, -> { with_status(:canceled) } + scope :skipped, -> { with_status(:skipped) } + scope :manual, -> { with_status(:manual) } + scope :scheduled, -> { with_status(:scheduled) } + scope :alive, -> { with_status(:created, :preparing, :pending, :running) } + scope :created_or_pending, -> { with_status(:created, :pending) } + scope :running_or_pending, -> { with_status(:running, :pending) } + scope :finished, -> { with_status(:success, :failed, :canceled) } + scope :failed_or_canceled, -> { with_status(:failed, :canceled) } scope :cancelable, -> do where(status: [:running, :preparing, :pending, :created, :scheduled]) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 299e413321d..952de92cae1 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -168,7 +168,7 @@ module Issuable # matched_columns - Modify the scope of the query. 'title', 'description' or joining them with a comma. # # Returns an ActiveRecord::Relation. - def full_search(query, matched_columns: 'title,description') + def full_search(query, matched_columns: 'title,description', use_minimum_char_limit: true) allowed_columns = [:title, :description] matched_columns = matched_columns.to_s.split(',').map(&:to_sym) matched_columns &= allowed_columns @@ -176,7 +176,7 @@ module Issuable # Matching title or description if the matched_columns did not contain any allowed columns. matched_columns = [:title, :description] if matched_columns.empty? - fuzzy_search(query, matched_columns) + fuzzy_search(query, matched_columns, use_minimum_char_limit: use_minimum_char_limit) end def simple_sorts diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 0d88b34fb48..2f3f9b399d9 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -63,6 +63,9 @@ module Mentionable skip_project_check: skip_project_check? ).merge(mentionable_params) + cached_html = self.try(:updated_cached_html_for, attr.to_sym) + options[:rendered] = cached_html if cached_html + extractor.analyze(text, options) end diff --git a/app/models/concerns/project_api_compatibility.rb b/app/models/concerns/project_api_compatibility.rb new file mode 100644 index 00000000000..cb00efb06df --- /dev/null +++ b/app/models/concerns/project_api_compatibility.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# Add methods used by the projects API +module ProjectAPICompatibility + extend ActiveSupport::Concern + + def build_git_strategy=(value) + write_attribute(:build_allow_git_fetch, value == 'fetch') + end + + def auto_devops_enabled=(value) + self.build_auto_devops if self.auto_devops&.enabled.nil? + self.auto_devops.update! enabled: value + end + + def auto_devops_deploy_strategy=(value) + self.build_auto_devops if self.auto_devops&.enabled.nil? + self.auto_devops.update! deploy_strategy: value + end +end diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb index f268a842db4..551a2e56ecf 100644 --- a/app/models/concerns/project_features_compatibility.rb +++ b/app/models/concerns/project_features_compatibility.rb @@ -9,32 +9,70 @@ require 'gitlab/utils' module ProjectFeaturesCompatibility extend ActiveSupport::Concern + # TODO: remove in API v5, replaced by *_access_level def wiki_enabled=(value) - write_feature_attribute(:wiki_access_level, value) + write_feature_attribute_boolean(:wiki_access_level, value) end + # TODO: remove in API v5, replaced by *_access_level def builds_enabled=(value) - write_feature_attribute(:builds_access_level, value) + write_feature_attribute_boolean(:builds_access_level, value) end + # TODO: remove in API v5, replaced by *_access_level def merge_requests_enabled=(value) - write_feature_attribute(:merge_requests_access_level, value) + write_feature_attribute_boolean(:merge_requests_access_level, value) end + # TODO: remove in API v5, replaced by *_access_level def issues_enabled=(value) - write_feature_attribute(:issues_access_level, value) + write_feature_attribute_boolean(:issues_access_level, value) end + # TODO: remove in API v5, replaced by *_access_level def snippets_enabled=(value) - write_feature_attribute(:snippets_access_level, value) + write_feature_attribute_boolean(:snippets_access_level, value) + end + + def repository_access_level=(value) + write_feature_attribute_string(:repository_access_level, value) + end + + def wiki_access_level=(value) + write_feature_attribute_string(:wiki_access_level, value) + end + + def builds_access_level=(value) + write_feature_attribute_string(:builds_access_level, value) + end + + def merge_requests_access_level=(value) + write_feature_attribute_string(:merge_requests_access_level, value) + end + + def issues_access_level=(value) + write_feature_attribute_string(:issues_access_level, value) + end + + def snippets_access_level=(value) + write_feature_attribute_string(:snippets_access_level, value) end private - def write_feature_attribute(field, value) + def write_feature_attribute_boolean(field, value) + access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED + write_feature_attribute_raw(field, access_level) + end + + def write_feature_attribute_string(field, value) + access_level = ProjectFeature.access_level_from_str(value) + write_feature_attribute_raw(field, access_level) + end + + def write_feature_attribute_raw(field, value) build_project_feature unless project_feature - access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED - project_feature.__send__(:write_attribute, field, access_level) # rubocop:disable GitlabSecurity/PublicSend + project_feature.__send__(:write_attribute, field, value) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb index 6c3962b4c4f..d91be73d6f0 100644 --- a/app/models/concerns/reactive_caching.rb +++ b/app/models/concerns/reactive_caching.rb @@ -173,12 +173,16 @@ module ReactiveCaching end def within_reactive_cache_lifetime?(*args) - !!Rails.cache.read(alive_reactive_cache_key(*args)) + if Feature.enabled?(:reactive_caching_check_key_exists, default_enabled: true) + Rails.cache.exist?(alive_reactive_cache_key(*args)) + else + !!Rails.cache.read(alive_reactive_cache_key(*args)) + end end def enqueuing_update(*args) yield - ensure + ReactiveCachingWorker.perform_in(self.class.reactive_cache_refresh_interval, self.class, id, *args) end end diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb index 22b6b1d720c..e4fe46d722a 100644 --- a/app/models/concerns/relative_positioning.rb +++ b/app/models/concerns/relative_positioning.rb @@ -179,7 +179,7 @@ module RelativePositioning relation = yield relation if block_given? relation - .pluck(self.class.parent_column, "#{calculation}(relative_position) AS position") + .pluck(self.class.parent_column, Arel.sql("#{calculation}(relative_position) AS position")) .first&. last end diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index b9ffc64e4a9..9becab632f3 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -46,7 +46,7 @@ module Routable # See https://gitlab.com/gitlab-org/gitlab-ce/issues/18603. Also note that # our unique index is case-sensitive in Postgres. binary = Gitlab::Database.mysql? ? 'BINARY' : '' - order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)" + order_sql = Arel.sql("(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)") found = where_full_path_in([path]).reorder(order_sql).take return found if found diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb index a15dc19e07a..78544405c49 100644 --- a/app/models/concerns/storage/legacy_namespace.rb +++ b/app/models/concerns/storage/legacy_namespace.rb @@ -64,7 +64,7 @@ module Storage unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path) - Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" + Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" # rubocop:disable Gitlab/RailsLogger # if we cannot move namespace directory we should rollback # db changes in order to prevent out of sync between db and fs diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb index 8c769be0489..1293df571a3 100644 --- a/app/models/concerns/token_authenticatable.rb +++ b/app/models/concerns/token_authenticatable.rb @@ -52,7 +52,7 @@ module TokenAuthenticatable mod.define_method("#{token_field}_matches?") do |other_token| token = read_attribute(token_field) - token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(other_token, token) + token.present? && ActiveSupport::SecurityUtils.secure_compare(other_token, token) end end diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb deleted file mode 100644 index d0f5b6970b1..00000000000 --- a/app/models/cycle_analytics.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -class CycleAnalytics - STAGES = %i[issue plan code test review staging production].freeze - - def initialize(project, options) - @project = project - @options = options - end - - def all_medians_per_stage - STAGES.each_with_object({}) do |stage_name, medians_per_stage| - medians_per_stage[stage_name] = self[stage_name].median - end - end - - def summary - @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(@project, - from: @options[:from], - current_user: @options[:current_user]).data - end - - def stats - @stats ||= stats_per_stage - end - - def no_stats? - stats.all? { |hash| hash[:value].nil? } - end - - def permissions(user:) - Gitlab::CycleAnalytics::Permissions.get(user: user, project: @project) - end - - def [](stage_name) - Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options) - end - - private - - def stats_per_stage - STAGES.map do |stage_name| - self[stage_name].as_json - end - end -end diff --git a/app/models/cycle_analytics/base.rb b/app/models/cycle_analytics/base.rb new file mode 100644 index 00000000000..d7b28cd1b67 --- /dev/null +++ b/app/models/cycle_analytics/base.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module CycleAnalytics + class Base + STAGES = %i[issue plan code test review staging production].freeze + + def all_medians_by_stage + STAGES.each_with_object({}) do |stage_name, medians_per_stage| + medians_per_stage[stage_name] = self[stage_name].median + end + end + + def stats + @stats ||= STAGES.map do |stage_name| + self[stage_name].as_json + end + end + + def no_stats? + stats.all? { |hash| hash[:value].nil? } + end + + def [](stage_name) + Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options) + end + end +end diff --git a/app/models/cycle_analytics/project_level.rb b/app/models/cycle_analytics/project_level.rb new file mode 100644 index 00000000000..22631cc7d41 --- /dev/null +++ b/app/models/cycle_analytics/project_level.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module CycleAnalytics + class ProjectLevel < Base + attr_reader :project, :options + + def initialize(project, options:) + @project = project + @options = options + end + + def summary + @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project, + from: options[:from], + current_user: options[:current_user]).data + end + + def permissions(user:) + Gitlab::CycleAnalytics::Permissions.get(user: user, project: project) + end + end +end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index a8f5642f726..b69cda4f2f9 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -85,11 +85,6 @@ class Deployment < ApplicationRecord Commit.truncate_sha(sha) end - # Deprecated - will be replaced by a persisted cluster_id - def deployment_platform_cluster - environment.deployment_platform&.cluster - end - def execute_hooks deployment_data = Gitlab::DataBuilder::Deployment.build(self) project.execute_services(deployment_data, :deployment_hooks) @@ -176,45 +171,8 @@ class Deployment < ApplicationRecord deployed_at&.to_time&.in_time_zone&.to_s(:medium) end - def has_metrics? - success? && prometheus_adapter&.can_query? - end - - def metrics - return {} unless has_metrics? - - metrics = prometheus_adapter.query(:deployment, self) - metrics&.merge(deployment_time: finished_at.to_i) || {} - end - - def additional_metrics - return {} unless has_metrics? - - metrics = prometheus_adapter.query(:additional_metrics_deployment, self) - metrics&.merge(deployment_time: finished_at.to_i) || {} - end - private - def prometheus_adapter - service = project.find_or_initialize_service('prometheus') - - if service.can_query? - service - else - cluster_prometheus - end - end - - # TODO remove fallback case to deployment_platform_cluster. - # Otherwise we will continue to pay the performance penalty described in - # https://gitlab.com/gitlab-org/gitlab-ce/issues/63475 - def cluster_prometheus - cluster_with_fallback = cluster || deployment_platform_cluster - - cluster_with_fallback.application_prometheus if cluster_with_fallback&.application_prometheus_available? - end - def ref_path File.join(environment.ref_path, 'deployments', iid.to_s) end diff --git a/app/models/deployment_metrics.rb b/app/models/deployment_metrics.rb new file mode 100644 index 00000000000..cfe762ca25e --- /dev/null +++ b/app/models/deployment_metrics.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +class DeploymentMetrics + include Gitlab::Utils::StrongMemoize + + attr_reader :project, :deployment + + delegate :cluster, to: :deployment + + def initialize(project, deployment) + @project = project + @deployment = deployment + end + + def has_metrics? + deployment.success? && prometheus_adapter&.can_query? + end + + def metrics + return {} unless has_metrics? + + metrics = prometheus_adapter.query(:deployment, deployment) + metrics&.merge(deployment_time: deployment.finished_at.to_i) || {} + end + + def additional_metrics + return {} unless has_metrics? + + metrics = prometheus_adapter.query(:additional_metrics_deployment, deployment) + metrics&.merge(deployment_time: deployment.finished_at.to_i) || {} + end + + private + + def prometheus_adapter + strong_memoize(:prometheus_adapter) do + service = project.find_or_initialize_service('prometheus') + + if service.can_query? + service + else + cluster_prometheus + end + end + end + + # TODO remove fallback case to deployment_platform_cluster. + # Otherwise we will continue to pay the performance penalty described in + # https://gitlab.com/gitlab-org/gitlab-ce/issues/63475 + # + # Removal issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/64105 + def cluster_prometheus + cluster_with_fallback = cluster || deployment_platform_cluster + + cluster_with_fallback.application_prometheus if cluster_with_fallback&.application_prometheus_available? + end + + def deployment_platform_cluster + deployment.environment.deployment_platform&.cluster + end +end diff --git a/app/models/discussion.rb b/app/models/discussion.rb index ae13cdfd85f..dd896f77084 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -38,6 +38,17 @@ class Discussion grouped_notes.values.map { |notes| build(notes, context_noteable) } end + def self.lazy_find(discussion_id) + BatchLoader.for(discussion_id).batch do |discussion_ids, loader| + results = Note.where(discussion_id: discussion_ids).fresh.to_a.group_by(&:discussion_id) + results.each do |discussion_id, notes| + next if notes.empty? + + loader.call(discussion_id, Discussion.build(notes)) + end + end + end + # Returns an alphanumeric discussion ID based on `build_discussion_id` def self.discussion_id(note) Digest::SHA1.hexdigest(build_discussion_id(note).join("-")) diff --git a/app/models/email.rb b/app/models/email.rb index 0ddaa049c3b..580633d3232 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -4,9 +4,8 @@ class Email < ApplicationRecord include Sortable include Gitlab::SQL::Pattern - belongs_to :user + belongs_to :user, optional: false - validates :user_id, presence: true validates :email, presence: true, uniqueness: true, devise_email: true validate :unique_email, if: ->(email) { email.email_changed? } diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb index 2fb6cadc8cd..d7dc64190d6 100644 --- a/app/models/environment_status.rb +++ b/app/models/environment_status.rb @@ -3,11 +3,10 @@ class EnvironmentStatus include Gitlab::Utils::StrongMemoize - attr_reader :environment, :merge_request, :sha + attr_reader :project, :environment, :merge_request, :sha delegate :id, to: :environment delegate :name, to: :environment - delegate :project, to: :environment delegate :status, to: :deployment, allow_nil: true delegate :deployed_at, to: :deployment, allow_nil: true @@ -21,7 +20,8 @@ class EnvironmentStatus build_environments_status(mr, user, mr.merge_pipeline) end - def initialize(environment, merge_request, sha) + def initialize(project, environment, merge_request, sha) + @project = project @environment = environment @merge_request = merge_request @sha = sha @@ -33,8 +33,14 @@ class EnvironmentStatus end end + def has_metrics? + strong_memoize(:has_metrics) do + deployment_metrics.has_metrics? + end + end + def changes - return [] if project.route_map_for(sha).nil? + return [] unless has_route_map? changed_files.map { |file| build_change(file) }.compact end @@ -44,10 +50,18 @@ class EnvironmentStatus .merge_request_diff_files.where(deleted_file: false) end + def has_route_map? + project.route_map_for(sha).present? + end + private PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze + def deployment_metrics + @deployment_metrics ||= DeploymentMetrics.new(project, deployment) + end + def build_change(file) public_path = project.public_path_for_source_path(file.new_path, sha) return if public_path.nil? @@ -67,7 +81,7 @@ class EnvironmentStatus pipeline.environments.available.map do |environment| next unless Ability.allowed?(user, :read_environment, environment) - EnvironmentStatus.new(environment, mr, pipeline.sha) + EnvironmentStatus.new(pipeline.project, environment, mr, pipeline.sha) end.compact end private_class_method :build_environments_status diff --git a/app/models/label_note.rb b/app/models/label_note.rb index d6814f4a948..ba5f1f82a81 100644 --- a/app/models/label_note.rb +++ b/app/models/label_note.rb @@ -62,19 +62,27 @@ class LabelNote < Note end def note_text(html: false) - added = labels_str('added', label_refs_by_action('add', html)) - removed = labels_str('removed', label_refs_by_action('remove', html)) + added = labels_str(label_refs_by_action('add', html), prefix: 'added', suffix: added_suffix) + removed = labels_str(label_refs_by_action('remove', html), prefix: removed_prefix) [added, removed].compact.join(' and ') end + def removed_prefix + 'removed' + end + + def added_suffix + '' + end + # returns string containing added/removed labels including # count of deleted labels: # # added ~1 ~2 + 1 deleted label # added 3 deleted labels # added ~1 ~2 labels - def labels_str(prefix, label_refs) + def labels_str(label_refs, prefix: '', suffix: '') existing_refs = label_refs.select { |ref| ref.present? }.sort refs_str = existing_refs.empty? ? nil : existing_refs.join(' ') @@ -84,9 +92,9 @@ class LabelNote < Note return unless refs_str || deleted_str label_list_str = [refs_str, deleted_str].compact.join(' + ') - suffix = 'label'.pluralize(deleted > 0 ? deleted : existing_refs.count) + suffix += ' label'.pluralize(deleted > 0 ? deleted : existing_refs.count) - "#{prefix} #{label_list_str} #{suffix}" + "#{prefix} #{label_list_str} #{suffix.squish}" end def label_refs_by_action(action, html) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e96e26cc773..53977748c30 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1127,6 +1127,19 @@ class MergeRequest < ApplicationRecord "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/merge" end + def train_ref_path + "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/train" + end + + def cleanup_refs(only: :all) + target_refs = [] + target_refs << ref_path if %i[all head].include?(only) + target_refs << merge_ref_path if %i[all merge].include?(only) + target_refs << train_ref_path if %i[all train].include?(only) + + project.repository.delete_refs(*target_refs) + end + def self.merge_request_ref?(ref) ref.start_with?("refs/#{Repository::REF_MERGE_REQUEST}/") end diff --git a/app/models/merge_requests_closing_issues.rb b/app/models/merge_requests_closing_issues.rb index 22cedf57b86..5c53cfd8c27 100644 --- a/app/models/merge_requests_closing_issues.rb +++ b/app/models/merge_requests_closing_issues.rb @@ -25,7 +25,7 @@ class MergeRequestsClosingIssues < ApplicationRecord class << self def count_for_collection(ids, current_user) - closing_merge_requests(ids, current_user).group(:issue_id).pluck('issue_id', 'COUNT(*) as count') + closing_merge_requests(ids, current_user).group(:issue_id).pluck('issue_id', Arel.sql('COUNT(*) as count')) end def count_for_issue(id, current_user) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index af50293a179..1d95590bac9 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -41,8 +41,7 @@ class Namespace < ApplicationRecord validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :name, presence: true, - length: { maximum: 255 }, - namespace_name: true + length: { maximum: 255 } validates :description, length: { maximum: 255 } validates :path, diff --git a/app/models/note.rb b/app/models/note.rb index 4e9ea146485..5c31cff9816 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -158,6 +158,8 @@ class Note < ApplicationRecord Discussion.build_collection(all.includes(:noteable).fresh, context_noteable) end + # Note: Where possible consider using Discussion#lazy_find to return + # Discussions in order to benefit from having records batch loaded. def find_discussion(discussion_id) notes = where(discussion_id: discussion_id).fresh.to_a diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index d6d879c6d89..e6e491634ab 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -12,9 +12,11 @@ class PagesDomain < ApplicationRecord validates :domain, hostname: { allow_numeric_hostname: true } validates :domain, uniqueness: { case_sensitive: false } - validates :certificate, presence: { message: 'must be present if HTTPS-only is enabled' }, if: ->(domain) { domain.project&.pages_https_only? } + validates :certificate, presence: { message: 'must be present if HTTPS-only is enabled' }, + if: :certificate_should_be_present? validates :certificate, certificate: true, if: ->(domain) { domain.certificate.present? } - validates :key, presence: { message: 'must be present if HTTPS-only is enabled' }, if: ->(domain) { domain.project&.pages_https_only? } + validates :key, presence: { message: 'must be present if HTTPS-only is enabled' }, + if: :certificate_should_be_present? validates :key, certificate_key: true, if: ->(domain) { domain.key.present? } validates :verification_code, presence: true, allow_blank: false @@ -249,4 +251,8 @@ class PagesDomain < ApplicationRecord rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError nil end + + def certificate_should_be_present? + !auto_ssl_enabled? && project&.pages_https_only? + end end diff --git a/app/models/project.rb b/app/models/project.rb index 0f4fba5d0b6..2a29cb1ae09 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -15,6 +15,7 @@ class Project < ApplicationRecord include CaseSensitivity include TokenAuthenticatable include ValidAttribute + include ProjectAPICompatibility include ProjectFeaturesCompatibility include SelectForProjectAuthorization include Presentable @@ -356,7 +357,7 @@ class Project < ApplicationRecord scope :with_unmigrated_storage, -> { where('storage_version < :version OR storage_version IS NULL', version: LATEST_STORAGE_VERSION) } # last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push - scope :sorted_by_activity, -> { reorder("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC") } + scope :sorted_by_activity, -> { reorder(Arel.sql("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC")) } scope :sorted_by_stars_desc, -> { reorder(star_count: :desc) } scope :sorted_by_stars_asc, -> { reorder(star_count: :asc) } @@ -611,7 +612,7 @@ class Project < ApplicationRecord end end - def initialize(attributes = {}) + def initialize(attributes = nil) # We can't use default_value_for because the database has a default # value of 0 for visibility_level. If someone attempts to create a # private project, default_value_for will assume that the @@ -621,6 +622,8 @@ class Project < ApplicationRecord # # To fix the problem, we assign the actual default in the application if # no explicit visibility has been initialized. + attributes ||= {} + unless visibility_attribute_present?(attributes) attributes[:visibility_level] = Gitlab::CurrentSettings.default_project_visibility end @@ -687,10 +690,6 @@ class Project < ApplicationRecord { scope: :project, status: auto_devops&.enabled || Feature.enabled?(:force_autodevops_on_by_default, self) } end - def multiple_mr_assignees_enabled? - Feature.enabled?(:multiple_merge_request_assignees, self) - end - def daily_statistics_enabled? Feature.enabled?(:project_daily_statistics, self, default_enabled: true) end @@ -783,6 +782,7 @@ class Project < ApplicationRecord job_id end + # rubocop:disable Gitlab/RailsLogger def log_import_activity(job_id, type: :import) job_type = type.to_s.capitalize @@ -792,6 +792,7 @@ class Project < ApplicationRecord Rails.logger.error("#{job_type} job failed to create for #{full_path}.") end end + # rubocop:enable Gitlab/RailsLogger def reset_cache_and_import_attrs run_after_commit do @@ -1554,7 +1555,7 @@ class Project < ApplicationRecord end def valid_runners_token?(token) - self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) + self.runners_token && ActiveSupport::SecurityUtils.secure_compare(token, self.runners_token) end # rubocop: disable CodeReuse/ServiceClass @@ -1664,6 +1665,7 @@ class Project < ApplicationRecord end # rubocop: enable CodeReuse/ServiceClass + # rubocop:disable Gitlab/RailsLogger def write_repository_config(gl_full_path: full_path) # We'd need to keep track of project full path otherwise directory tree # created with hashed storage enabled cannot be usefully imported using @@ -1673,6 +1675,7 @@ class Project < ApplicationRecord Rails.logger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.") nil end + # rubocop:enable Gitlab/RailsLogger def after_import repository.after_import @@ -1714,6 +1717,7 @@ class Project < ApplicationRecord @pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self) end + # rubocop:disable Gitlab/RailsLogger def add_export_job(current_user:, after_export_strategy: nil, params: {}) job_id = ProjectExportWorker.perform_async(current_user.id, self.id, after_export_strategy, params) @@ -1723,6 +1727,7 @@ class Project < ApplicationRecord Rails.logger.error "Export job failed to start for project ID #{self.id}" end end + # rubocop:enable Gitlab/RailsLogger def import_export_shared @import_export_shared ||= Gitlab::ImportExport::Shared.new(self) @@ -1913,9 +1918,8 @@ class Project < ApplicationRecord @route_maps_by_commit ||= Hash.new do |h, sha| h[sha] = begin data = repository.route_map_for(sha) - next unless data - Gitlab::RouteMap.new(data) + Gitlab::RouteMap.new(data) if data rescue Gitlab::RouteMap::FormatError nil end @@ -2144,7 +2148,7 @@ class Project < ApplicationRecord public? && repository_exists? && Gitlab::CurrentSettings.hashed_storage_enabled && - Feature.enabled?(:object_pools, self) + Feature.enabled?(:object_pools, self, default_enabled: true) end def leave_pool_repository diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 0542581c6e0..7ff06655de0 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -24,6 +24,12 @@ class ProjectFeature < ApplicationRecord FEATURES = %i(issues merge_requests wiki snippets builds repository pages).freeze PRIVATE_FEATURES_MIN_ACCESS_LEVEL = { merge_requests: Gitlab::Access::REPORTER }.freeze + STRING_OPTIONS = HashWithIndifferentAccess.new({ + 'disabled' => DISABLED, + 'private' => PRIVATE, + 'enabled' => ENABLED, + 'public' => PUBLIC + }).freeze class << self def access_level_attribute(feature) @@ -45,6 +51,14 @@ class ProjectFeature < ApplicationRecord PRIVATE_FEATURES_MIN_ACCESS_LEVEL.fetch(feature, Gitlab::Access::GUEST) end + def access_level_from_str(level) + STRING_OPTIONS.fetch(level) + end + + def str_from_access_level(level) + STRING_OPTIONS.key(level) + end + private def ensure_feature!(feature) @@ -83,6 +97,10 @@ class ProjectFeature < ApplicationRecord public_send(ProjectFeature.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend end + def string_access_level(feature) + ProjectFeature.str_from_access_level(access_level(feature)) + end + def builds_enabled? builds_access_level > DISABLED end diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb index 1605345efd5..23adffb33d8 100644 --- a/app/models/project_import_state.rb +++ b/app/models/project_import_state.rb @@ -65,7 +65,7 @@ class ProjectImportState < ApplicationRecord update_column(:last_error, sanitized_message) rescue ActiveRecord::ActiveRecordError => e - Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}") + Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}") # rubocop:disable Gitlab/RailsLogger ensure @errors = original_errors end diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index f0ef2d925ab..47106d7bdbb 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -7,7 +7,7 @@ class CiService < Service default_value_for :category, 'ci' def valid_token?(token) - self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) + self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.secure_compare(token, self.token) end def self.supported_events diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index dbdc8345c93..d2660051343 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -46,7 +46,7 @@ class DroneCiService < CiService end def commit_status(sha, ref) - with_reactive_cache(sha, ref) {|cached| cached[:commit_status] } + with_reactive_cache(sha, ref) { |cached| cached[:commit_status] } end def calculate_reactive_cache(sha, ref) @@ -68,7 +68,7 @@ class DroneCiService < CiService end { commit_status: status } - rescue Errno::ECONNREFUSED + rescue *Gitlab::HTTP::HTTP_ERRORS { commit_status: :error } end diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb index bfabc6d262c..5f5cff97808 100644 --- a/app/models/project_services/slash_commands_service.rb +++ b/app/models/project_services/slash_commands_service.rb @@ -12,7 +12,7 @@ class SlashCommandsService < Service def valid_token?(token) self.respond_to?(:token) && self.token.present? && - ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) + ActiveSupport::SecurityUtils.secure_compare(token, self.token) end def self.supported_events diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb index 8a179b4d56d..3802d258664 100644 --- a/app/models/project_statistics.rb +++ b/app/models/project_statistics.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ProjectStatistics < ApplicationRecord + include AfterCommitQueue + belongs_to :project belongs_to :namespace @@ -15,6 +17,7 @@ class ProjectStatistics < ApplicationRecord COLUMNS_TO_REFRESH = [:repository_size, :wiki_size, :lfs_objects_size, :commit_count].freeze INCREMENTABLE_COLUMNS = { build_artifacts_size: %i[storage_size], packages_size: %i[storage_size] }.freeze + NAMESPACE_RELATABLE_COLUMNS = [:repository_size, :wiki_size, :lfs_objects_size].freeze scope :for_project_ids, ->(project_ids) { where(project_id: project_ids) } @@ -22,13 +25,17 @@ class ProjectStatistics < ApplicationRecord repository_size + lfs_objects_size end - def refresh!(only: nil) + def refresh!(only: []) COLUMNS_TO_REFRESH.each do |column, generator| - if only.blank? || only.include?(column) + if only.empty? || only.include?(column) public_send("update_#{column}") # rubocop:disable GitlabSecurity/PublicSend end end + if only.empty? || only.any? { |column| NAMESPACE_RELATABLE_COLUMNS.include?(column) } + schedule_namespace_aggregation_worker + end + save! end @@ -81,4 +88,18 @@ class ProjectStatistics < ApplicationRecord update_all(updates.join(', ')) end + + private + + def schedule_namespace_aggregation_worker + run_after_commit do + next unless schedule_aggregation_worker? + + Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id) + end + end + + def schedule_aggregation_worker? + Feature.enabled?(:update_statistics_namespace, project&.root_ancestor) + end end diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb index 62090444f79..b8e7673dcf5 100644 --- a/app/models/prometheus_metric.rb +++ b/app/models/prometheus_metric.rb @@ -3,68 +3,7 @@ class PrometheusMetric < ApplicationRecord belongs_to :project, validate: true, inverse_of: :prometheus_metrics - enum group: { - # built-in groups - nginx_ingress_vts: -1, - ha_proxy: -2, - aws_elb: -3, - nginx: -4, - kubernetes: -5, - nginx_ingress: -6, - - # custom/user groups - business: 0, - response: 1, - system: 2 - } - - GROUP_DETAILS = { - # 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), - 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), - priority: 10 - }.freeze, - ha_proxy: { - group_title: _('Response metrics (HA Proxy)'), - 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), - priority: 10 - }.freeze, - nginx: { - group_title: _('Response metrics (NGINX)'), - 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), - priority: 5 - }.freeze, - - # custom/user groups - business: { - group_title: _('Business metrics (Custom)'), - priority: 0 - }.freeze, - response: { - group_title: _('Response metrics (Custom)'), - priority: -5 - }.freeze, - system: { - group_title: _('System metrics (Custom)'), - priority: -10 - }.freeze - }.freeze + enum group: PrometheusMetricEnums.groups validates :title, presence: true validates :query, presence: true @@ -121,6 +60,6 @@ class PrometheusMetric < ApplicationRecord private def group_details(group) - GROUP_DETAILS.fetch(group.to_sym) + PrometheusMetricEnums.group_details.fetch(group.to_sym) end end diff --git a/app/models/prometheus_metric_enums.rb b/app/models/prometheus_metric_enums.rb new file mode 100644 index 00000000000..6cb22cc69cd --- /dev/null +++ b/app/models/prometheus_metric_enums.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module PrometheusMetricEnums + def self.groups + { + # built-in groups + nginx_ingress_vts: -1, + ha_proxy: -2, + aws_elb: -3, + nginx: -4, + kubernetes: -5, + nginx_ingress: -6, + + # custom/user groups + business: 0, + response: 1, + system: 2 + } + end + + def self.group_details + { + # 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), + 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), + priority: 10 + }.freeze, + ha_proxy: { + group_title: _('Response metrics (HA Proxy)'), + 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), + priority: 10 + }.freeze, + nginx: { + group_title: _('Response metrics (NGINX)'), + 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), + priority: 5 + }.freeze, + + # custom/user groups + business: { + group_title: _('Business metrics (Custom)'), + priority: 0 + }.freeze, + response: { + group_title: _('Response metrics (Custom)'), + priority: -5 + }.freeze, + system: { + group_title: _('System metrics (Custom)'), + priority: -10 + }.freeze + }.freeze + end +end diff --git a/app/models/repository.rb b/app/models/repository.rb index a408db7ebbe..187382ad182 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -273,7 +273,7 @@ class Repository # This will still fail if the file is corrupted (e.g. 0 bytes) raw_repository.write_ref(keep_around_ref_name(sha), sha) rescue Gitlab::Git::CommandError => ex - Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}" + Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}" # rubocop:disable Gitlab/RailsLogger end end @@ -845,6 +845,10 @@ class Repository raw.merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref) end + def delete_refs(*ref_names) + raw.delete_refs(*ref_names) + end + def ff_merge(user, source, target_branch, merge_request: nil) their_commit_id = commit(source)&.id raise 'Invalid merge source' if their_commit_id.nil? @@ -930,6 +934,7 @@ class Repository async_remove_remote(remote_name) if tmp_remote_name end + # rubocop:disable Gitlab/RailsLogger def async_remove_remote(remote_name) return unless remote_name @@ -943,6 +948,7 @@ class Repository job_id end + # rubocop:enable Gitlab/RailsLogger def fetch_source_branch!(source_repository, source_branch, local_ref) raw_repository.fetch_source_branch!(source_repository.raw_repository, source_branch, local_ref) diff --git a/app/models/resource_label_event.rb b/app/models/resource_label_event.rb index f2c7cb6a65d..ad08f4763ae 100644 --- a/app/models/resource_label_event.rb +++ b/app/models/resource_label_event.rb @@ -36,10 +36,9 @@ class ResourceLabelEvent < ApplicationRecord issue || merge_request end - # create same discussion id for all actions with the same user and time def discussion_id(resource = nil) strong_memoize(:discussion_id) do - Digest::SHA1.hexdigest([self.class.name, created_at, user_id].join("-")) + Digest::SHA1.hexdigest(discussion_id_key.join("-")) end end @@ -121,4 +120,8 @@ class ResourceLabelEvent < ApplicationRecord def resource_parent issuable.project || issuable.group end + + def discussion_id_key + [self.class.name, created_at, user_id] + end end diff --git a/app/models/ssh_host_key.rb b/app/models/ssh_host_key.rb index b6fb39ee81f..9bd35d30845 100644 --- a/app/models/ssh_host_key.rb +++ b/app/models/ssh_host_key.rb @@ -106,7 +106,7 @@ class SshHostKey if status.success? && !errors.present? { known_hosts: known_hosts } else - Rails.logger.debug("Failed to detect SSH host keys for #{id}: #{errors}") + Rails.logger.debug("Failed to detect SSH host keys for #{id}: #{errors}") # rubocop:disable Gitlab/RailsLogger { error: 'Failed to detect SSH host keys' } end diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb index b483c677be9..928c773c307 100644 --- a/app/models/storage/legacy_project.rb +++ b/app/models/storage/legacy_project.rb @@ -41,7 +41,7 @@ module Storage gitlab_shell.mv_repository(repository_storage, "#{old_full_path}.wiki", "#{new_full_path}.wiki") return true rescue => e - Rails.logger.error "Exception renaming #{old_full_path} -> #{new_full_path}: #{e}" + Rails.logger.error "Exception renaming #{old_full_path} -> #{new_full_path}: #{e}" # rubocop:disable Gitlab/RailsLogger # Returning false does not rollback after_* transaction but gives # us information about failing some of tasks return false diff --git a/app/models/uploads/base.rb b/app/models/uploads/base.rb index f9814159958..29f376670da 100644 --- a/app/models/uploads/base.rb +++ b/app/models/uploads/base.rb @@ -7,7 +7,7 @@ module Uploads attr_reader :logger def initialize(logger: nil) - @logger ||= Rails.logger + @logger ||= Rails.logger # rubocop:disable Gitlab/RailsLogger end def delete_keys_async(keys_to_delete) diff --git a/app/models/user.rb b/app/models/user.rb index 26be197209a..02637b70f03 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1117,9 +1117,10 @@ class User < ApplicationRecord def ensure_namespace_correct if namespace - namespace.path = namespace.name = username if username_changed? + namespace.path = username if username_changed? + namespace.name = name if name_changed? else - build_namespace(path: username, name: username) + build_namespace(path: username, name: name) end end |