summaryrefslogtreecommitdiff
path: root/app/models/concerns
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/concerns')
-rw-r--r--app/models/concerns/artifact_migratable.rb58
-rw-r--r--app/models/concerns/cache_markdown_field.rb88
-rw-r--r--app/models/concerns/maskable.rb4
-rw-r--r--app/models/concerns/milestoneish.rb20
-rw-r--r--app/models/concerns/noteable.rb12
-rw-r--r--app/models/concerns/referable.rb1
-rw-r--r--app/models/concerns/resolvable_note.rb2
-rw-r--r--app/models/concerns/taskable.rb7
-rw-r--r--app/models/concerns/update_project_statistics.rb52
9 files changed, 73 insertions, 171 deletions
diff --git a/app/models/concerns/artifact_migratable.rb b/app/models/concerns/artifact_migratable.rb
deleted file mode 100644
index 7c9f579b480..00000000000
--- a/app/models/concerns/artifact_migratable.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# frozen_string_literal: true
-
-# Adapter class to unify the interface between mounted uploaders and the
-# Ci::Artifact model
-# Meant to be prepended so the interface can stay the same
-module ArtifactMigratable
- def artifacts_file
- job_artifacts_archive&.file || legacy_artifacts_file
- end
-
- def artifacts_metadata
- job_artifacts_metadata&.file || legacy_artifacts_metadata
- end
-
- def artifacts?
- !artifacts_expired? && artifacts_file&.exists?
- end
-
- def artifacts_metadata?
- artifacts? && artifacts_metadata.exists?
- end
-
- def artifacts_file_changed?
- job_artifacts_archive&.file_changed? || attribute_changed?(:artifacts_file)
- end
-
- def remove_artifacts_file!
- if job_artifacts_archive
- job_artifacts_archive.destroy
- else
- remove_legacy_artifacts_file!
- end
- end
-
- def remove_artifacts_metadata!
- if job_artifacts_metadata
- job_artifacts_metadata.destroy
- else
- remove_legacy_artifacts_metadata!
- end
- end
-
- def artifacts_size
- read_attribute(:artifacts_size).to_i + job_artifacts.sum(:size).to_i
- end
-
- def legacy_artifacts_file
- return unless Feature.enabled?(:ci_enable_legacy_artifacts)
-
- super
- end
-
- def legacy_artifacts_metadata
- return unless Feature.enabled?(:ci_enable_legacy_artifacts)
-
- super
- end
-end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index f90cd1ea690..42203a5f214 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -13,43 +13,9 @@
module CacheMarkdownField
extend ActiveSupport::Concern
- # Increment this number every time the renderer changes its output
- CACHE_COMMONMARK_VERSION_START = 10
- CACHE_COMMONMARK_VERSION = 16
-
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
- # Knows about the relationship between markdown and html field names, and
- # stores the rendering contexts for the latter
- class FieldData
- def initialize
- @data = {}
- end
-
- delegate :[], :[]=, to: :@data
-
- def markdown_fields
- @data.keys
- end
-
- def html_field(markdown_field)
- "#{markdown_field}_html"
- end
-
- def html_fields
- markdown_fields.map { |field| html_field(field) }
- end
-
- def html_fields_whitelisted
- markdown_fields.each_with_object([]) do |field, fields|
- if @data[field].fetch(:whitelisted, false)
- fields << html_field(field)
- end
- end
- end
- end
-
def skip_project_check?
false
end
@@ -85,24 +51,22 @@ module CacheMarkdownField
end.to_h
updates['cached_markdown_version'] = latest_cached_markdown_version
- updates.each {|html_field, data| write_attribute(html_field, data) }
+ updates.each { |field, data| write_markdown_field(field, data) }
end
def refresh_markdown_cache!
updates = refresh_markdown_cache
- return unless persisted? && Gitlab::Database.read_write?
-
- update_columns(updates)
+ save_markdown(updates)
end
def cached_html_up_to_date?(markdown_field)
- html_field = cached_markdown_fields.html_field(markdown_field)
+ return false if cached_html_for(markdown_field).nil? && __send__(markdown_field).present? # rubocop:disable GitlabSecurity/PublicSend
- return false if cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil? # rubocop:disable GitlabSecurity/PublicSend
+ html_field = cached_markdown_fields.html_field(markdown_field)
- markdown_changed = attribute_changed?(markdown_field) || false
- html_changed = attribute_changed?(html_field) || false
+ markdown_changed = markdown_field_changed?(markdown_field)
+ html_changed = markdown_field_changed?(html_field)
latest_cached_markdown_version == cached_markdown_version &&
(html_changed || markdown_changed == html_changed)
@@ -117,21 +81,21 @@ module CacheMarkdownField
end
def cached_html_for(markdown_field)
- raise ArgumentError.new("Unknown field: #{field}") unless
+ raise ArgumentError.new("Unknown field: #{markdown_field}") unless
cached_markdown_fields.markdown_fields.include?(markdown_field)
__send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend
end
def latest_cached_markdown_version
- @latest_cached_markdown_version ||= (CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) | local_version
+ @latest_cached_markdown_version ||= (Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) | local_version
end
def local_version
# because local_markdown_version is stored in application_settings which
# uses cached_markdown_version too, we check explicitly to avoid
# endless loop
- return local_markdown_version if has_attribute?(:local_markdown_version)
+ return local_markdown_version if respond_to?(:has_attribute?) && has_attribute?(:local_markdown_version)
settings = Gitlab::CurrentSettings.current_application_settings
@@ -150,32 +114,14 @@ module CacheMarkdownField
included do
cattr_reader :cached_markdown_fields do
- FieldData.new
+ Gitlab::MarkdownCache::FieldData.new
end
- # Always exclude _html fields from attributes (including serialization).
- # They contain unredacted HTML, which would be a security issue
- alias_method :attributes_before_markdown_cache, :attributes
- def attributes
- attrs = attributes_before_markdown_cache
- html_fields = cached_markdown_fields.html_fields
- whitelisted = cached_markdown_fields.html_fields_whitelisted
- exclude_fields = html_fields - whitelisted
-
- exclude_fields.each do |field|
- attrs.delete(field)
- end
-
- if whitelisted.empty?
- attrs.delete('cached_markdown_version')
- end
-
- attrs
+ if self < ActiveRecord::Base
+ include Gitlab::MarkdownCache::ActiveRecord::Extension
+ else
+ prepend Gitlab::MarkdownCache::Redis::Extension
end
-
- # Using before_update here conflicts with elasticsearch-model somehow
- before_create :refresh_markdown_cache, if: :invalidated_markdown_cache?
- before_update :refresh_markdown_cache, if: :invalidated_markdown_cache?
end
class_methods do
@@ -193,10 +139,8 @@ 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
- 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 = changed_markdown_fields & [markdown_field.to_s, *INVALIDATED_BY]
+ invalidations.delete(markdown_field.to_s) if changed_markdown_fields.include?("#{markdown_field}_html")
!invalidations.empty? || !cached_html_up_to_date?(markdown_field)
end
end
diff --git a/app/models/concerns/maskable.rb b/app/models/concerns/maskable.rb
index 2943872ffab..e0f2c41b836 100644
--- a/app/models/concerns/maskable.rb
+++ b/app/models/concerns/maskable.rb
@@ -7,9 +7,9 @@ module Maskable
# * No escape characters
# * No variables
# * No spaces
- # * Minimal length of 8 characters
+ # * Minimal length of 8 characters from the Base64 alphabets (RFC4648)
# * Absolutely no fun is allowed
- REGEX = /\A\w{8,}\z/.freeze
+ REGEX = /\A[a-zA-Z0-9_+=\/-]{8,}\z/.freeze
included do
validates :masked, inclusion: { in: [true, false] }
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index e65bbb8ca07..3deb86da6cf 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -1,28 +1,20 @@
# frozen_string_literal: true
module Milestoneish
- def closed_items_count(user)
- memoize_per_user(user, :closed_items_count) do
- (count_issues_by_state(user)['closed'] || 0) + merge_requests.closed_and_merged.size
- end
- end
-
- def total_items_count(user)
- memoize_per_user(user, :total_items_count) do
- total_issues_count(user) + merge_requests.size
- end
- end
-
def total_issues_count(user)
count_issues_by_state(user).values.sum
end
+ def closed_issues_count(user)
+ count_issues_by_state(user)['closed'].to_i
+ end
+
def complete?(user)
- total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user)
+ total_issues_count(user) > 0 && total_issues_count(user) == closed_issues_count(user)
end
def percent_complete(user)
- ((closed_items_count(user) * 100) / total_items_count(user)).abs
+ closed_issues_count(user) * 100 / total_issues_count(user)
rescue ZeroDivisionError
0
end
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index bfd0c36942b..4b428b0af83 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -3,14 +3,16 @@
module Noteable
extend ActiveSupport::Concern
- # `Noteable` class names that support resolvable notes.
- RESOLVABLE_TYPES = %w(MergeRequest).freeze
-
class_methods do
# `Noteable` class names that support replying to individual notes.
def replyable_types
%w(Issue MergeRequest)
end
+
+ # `Noteable` class names that support resolvable notes.
+ def resolvable_types
+ %w(MergeRequest)
+ end
end
# The timestamp of the note (e.g. the :created_at or :updated_at attribute if provided via
@@ -36,7 +38,7 @@ module Noteable
end
def supports_resolvable_notes?
- RESOLVABLE_TYPES.include?(base_class_name)
+ self.class.resolvable_types.include?(base_class_name)
end
def supports_discussions?
@@ -131,3 +133,5 @@ module Noteable
)
end
end
+
+Noteable.extend(Noteable::ClassMethods)
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index 58143a32fdc..4a506146de3 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -73,6 +73,7 @@ module Referable
(?<url>
#{Regexp.escape(Gitlab.config.gitlab.url)}
\/#{Project.reference_pattern}
+ (?:\/\-)?
\/#{Regexp.escape(route)}
\/#{pattern}
(?<path>
diff --git a/app/models/concerns/resolvable_note.rb b/app/models/concerns/resolvable_note.rb
index 16ea330701d..2d2d5fb7168 100644
--- a/app/models/concerns/resolvable_note.rb
+++ b/app/models/concerns/resolvable_note.rb
@@ -12,7 +12,7 @@ module ResolvableNote
validates :resolved_by, presence: true, if: :resolved?
# Keep this scope in sync with `#potentially_resolvable?`
- scope :potentially_resolvable, -> { where(type: RESOLVABLE_TYPES).where(noteable_type: Noteable::RESOLVABLE_TYPES) }
+ scope :potentially_resolvable, -> { where(type: RESOLVABLE_TYPES).where(noteable_type: Noteable.resolvable_types) }
# Keep this scope in sync with `#resolvable?`
scope :resolvable, -> { potentially_resolvable.user }
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index 2f0e078c807..b42adad94ba 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -75,4 +75,11 @@ module Taskable
def task_status_short
task_status(short: true)
end
+
+ def task_completion_status
+ @task_completion_status ||= {
+ count: tasks.summary.item_count,
+ completed_count: tasks.summary.complete_count
+ }
+ end
end
diff --git a/app/models/concerns/update_project_statistics.rb b/app/models/concerns/update_project_statistics.rb
index 67e1f0ec930..1f881249322 100644
--- a/app/models/concerns/update_project_statistics.rb
+++ b/app/models/concerns/update_project_statistics.rb
@@ -5,43 +5,47 @@
# It deals with `ProjectStatistics.increment_statistic` making sure not to update statistics on a cascade delete from the
# project, and keeping track of value deltas on each save. It updates the DB only when a change is needed.
#
-# How to use
-# - Invoke `update_project_statistics stat: :a_project_statistics_column, attribute: :an_attr_to_track` in a model class body.
+# Example:
#
-# Expectation
-# - `attribute` must be an ActiveRecord attribute
+# module Ci
+# class JobArtifact < ApplicationRecord
+# include UpdateProjectStatistics
+#
+# update_project_statistics project_statistics_name: :build_artifacts_size
+# end
+# end
+#
+# Expectation:
+#
+# - `statistic_attribute` must be an ActiveRecord attribute
# - The model must implement `project` and `project_id`. i.e. direct Project relationship or delegation
+#
module UpdateProjectStatistics
extend ActiveSupport::Concern
class_methods do
- attr_reader :statistic_name, :statistic_attribute
+ attr_reader :project_statistics_name, :statistic_attribute
- # Configure the model to update +stat+ on ProjectStatistics when +attribute+ changes
+ # Configure the model to update `project_statistics_name` on ProjectStatistics,
+ # when `statistic_attribute` changes
+ #
+ # - project_statistics_name: A column of `ProjectStatistics` to update
+ # - statistic_attribute: An attribute of the current model, default to `size`
#
- # +stat+:: a column of ProjectStatistics to update
- # +attribute+:: an attribute of the current model, default to +:size+
- def update_project_statistics(stat:, attribute: :size)
- @statistic_name = stat
- @statistic_attribute = attribute
+ def update_project_statistics(project_statistics_name:, statistic_attribute: :size)
+ @project_statistics_name = project_statistics_name
+ @statistic_attribute = statistic_attribute
after_save(:update_project_statistics_after_save, if: :update_project_statistics_attribute_changed?)
after_destroy(:update_project_statistics_after_destroy, unless: :project_destroyed?)
end
+
private :update_project_statistics
end
included do
private
- def project_destroyed?
- project.pending_delete?
- end
-
- def update_project_statistics_attribute_changed?
- saved_change_to_attribute?(self.class.statistic_attribute)
- end
-
def update_project_statistics_after_save
attr = self.class.statistic_attribute
delta = read_attribute(attr).to_i - attribute_before_last_save(attr).to_i
@@ -49,12 +53,20 @@ module UpdateProjectStatistics
update_project_statistics(delta)
end
+ def update_project_statistics_attribute_changed?
+ saved_change_to_attribute?(self.class.statistic_attribute)
+ end
+
def update_project_statistics_after_destroy
update_project_statistics(-read_attribute(self.class.statistic_attribute).to_i)
end
+ def project_destroyed?
+ project.pending_delete?
+ end
+
def update_project_statistics(delta)
- ProjectStatistics.increment_statistic(project_id, self.class.statistic_name, delta)
+ ProjectStatistics.increment_statistic(project_id, self.class.project_statistics_name, delta)
end
end
end