Class: Milestone
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Milestone
- Includes:
- AfterCommitQueue, AtomicInternalId, EachBatch, FromUnion, IidRoutes, Importable, Milestoneish, Sortable, Spammable, Timebox, UpdatedAtFilterable
- Defined in:
- app/models/milestone.rb
Defined Under Namespace
Classes: Predefined
Constant Summary
Constants included from Milestoneish
Milestoneish::DISPLAY_ISSUES_LIMIT
Constants included from Timebox
Timebox::Any, Timebox::None, Timebox::Started, Timebox::Upcoming
Constants included from Gitlab::SQL::Pattern
Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_TERM
Constants included from CacheMarkdownField
CacheMarkdownField::INVALIDATED_BY
Constants included from AtomicInternalId
AtomicInternalId::MissingValueError
Constants inherited from ApplicationRecord
Constants included from HasCheckConstraints
HasCheckConstraints::NOT_NULL_CHECK_PATTERN
Constants included from ResetOnColumnErrors
ResetOnColumnErrors::MAX_RESET_PERIOD
Instance Attribute Summary
Attributes included from Importable
#importing, #user_contributions
Attributes included from CacheMarkdownField
#skip_markdown_cache_validation
Class Method Summary collapse
- .link_reference_pattern ⇒ Object
- .min_chars_for_partial_matching ⇒ Object
- .reference_pattern ⇒ Object
- .reference_prefix ⇒ Object
-
.search_title(query) ⇒ Object
Searches for timeboxes with a matching title.
- .sort_by_attribute(method) ⇒ Object
- .sort_with_expired_last(method) ⇒ Object
- .states_count(projects, groups = nil) ⇒ Object
- .upcoming_ids(projects, groups, legacy_filtering_logic: false) ⇒ Object
- .with_web_entity_associations ⇒ Object
Instance Method Summary collapse
- #author_id ⇒ Object
- #check_for_spam? ⇒ Boolean
- #for_display ⇒ Object
- #group_milestone? ⇒ Boolean
- #hook_attrs ⇒ Object
- #merge_requests_enabled? ⇒ Boolean
- #parent ⇒ Object
- #participants ⇒ Object
- #project_milestone? ⇒ Boolean
- #resource_parent ⇒ Object
- #subgroup_milestone? ⇒ Boolean
-
#to_reference(from = nil, format: :name, full: false, absolute_path: false) ⇒ Object
Returns the String necessary to reference a milestone in Markdown.
Methods included from AfterCommitQueue
#run_after_commit, #run_after_commit_or_now
Methods included from Spammable
#allow_possible_spam?, #check_for_spam, #clear_spam_flags!, #invalidate_if_spam, #needs_recaptcha!, #recaptcha_error!, #render_recaptcha?, #spam, #spam!, #spam_description, #spam_title, #spammable_attribute_changed?, #spammable_entity_type, #spammable_text, #submittable_as_spam?, #submittable_as_spam_by?, #supports_recaptcha?, #unrecoverable_spam_error!
Methods included from IidRoutes
Methods included from Milestoneish
#closed_issues_count, #complete?, #elapsed_days, #expired, #expired?, #expires_at, #human_total_time_estimate, #human_total_time_spent, #issue_labels_visible_by_user, #issue_participants_visible_by_user, #issues_visible_to_user, #merge_requests_visible_to_user, #milestone_issues, #opened_issues_count, #percent_complete, #remaining_days, #total_issues_count, #total_merge_requests_count, #total_time_estimate, #total_time_spent, #upcoming, #upcoming?
Methods included from Timebox
#name=, #reference_link_text, #safe_title, #timebox_name, #to_ability_name, #weight_available?
Methods included from StripAttribute
Methods included from Referable
#referable_inspect, #reference_link_text, #to_reference_base
Methods included from Gitlab::SQL::Pattern
Methods included from CacheMarkdownField
#attribute_invalidated?, #banzai_render_context, #cached_html_for, #cached_html_up_to_date?, #can_cache_field?, #invalidated_markdown_cache?, #latest_cached_markdown_version, #mentionable_attributes_changed?, #mentioned_filtered_user_ids_for, #parent_user, #refresh_markdown_cache, #refresh_markdown_cache!, #rendered_field_content, #skip_project_check?, #store_mentions!, #store_mentions?, #store_mentions_after_commit?, #updated_cached_html_for
Methods included from AtomicInternalId
group_init, #internal_id_read_scope, #internal_id_scope_attrs, #internal_id_scope_usage, namespace_init, project_init, scope_attrs, scope_usage
Methods inherited from ApplicationRecord
===, cached_column_list, #create_or_load_association, current_transaction, declarative_enum, default_select_columns, delete_all_returning, #deleted_from_database?, id_in, id_not_in, iid_in, nullable_column?, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order
Methods included from Organizations::Sharding
Methods included from ResetOnColumnErrors
#reset_on_union_error, #reset_on_unknown_attribute_error
Methods included from Gitlab::SensitiveSerializableHash
Class Method Details
.link_reference_pattern ⇒ Object
149 150 151 |
# File 'app/models/milestone.rb', line 149 def self.link_reference_pattern @link_reference_pattern ||= compose_link_reference_pattern('milestones', /(?<milestone>\d+)/) end |
.min_chars_for_partial_matching ⇒ Object
122 123 124 |
# File 'app/models/milestone.rb', line 122 def self.min_chars_for_partial_matching 2 end |
.reference_pattern ⇒ Object
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'app/models/milestone.rb', line 130 def self.reference_pattern # NOTE: The iid pattern only matches when all characters on the expression # are digits, so it will match %2 but not %2.1 because that's probably a # milestone name and we want it to be matched as such. @reference_pattern ||= %r{ (#{Project.reference_pattern})? #{Regexp.escape(reference_prefix)} (?: (?<milestone_iid> \d+(?!\S\w)\b # Integer-based milestone iid, or ) | (?<milestone_name> [^"\s\<]+\b | # String-based single-word milestone title, or "[^"]+" # String-based multi-word milestone surrounded in quotes ) ) }x end |
.reference_prefix ⇒ Object
126 127 128 |
# File 'app/models/milestone.rb', line 126 def self.reference_prefix '%' end |
.search_title(query) ⇒ Object
Searches for timeboxes with a matching title.
This method uses ILIKE on PostgreSQL
query - The search query as a String
Returns an ActiveRecord::Relation.
118 119 120 |
# File 'app/models/milestone.rb', line 118 def self.search_title(query) fuzzy_search(query, [:title]) end |
.sort_by_attribute(method) ⇒ Object
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'app/models/milestone.rb', line 177 def self.sort_by_attribute(method) sorted = case method.to_s when 'due_date_asc' reorder_by_due_date_asc when 'due_date_desc' reorder(arel_table[:due_date].desc.nulls_last) when 'name_asc' reorder(Arel::Nodes::Ascending.new(arel_table[:title].lower)) when 'name_desc' reorder(Arel::Nodes::Descending.new(arel_table[:title].lower)) when 'start_date_asc' reorder(arel_table[:start_date].asc.nulls_last) when 'start_date_desc' reorder(arel_table[:start_date].desc.nulls_last) else order_by(method) end sorted.with_order_id_desc end |
.sort_with_expired_last(method) ⇒ Object
199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'app/models/milestone.rb', line 199 def self.sort_with_expired_last(method) # NOTE: this is a custom ordering of milestones # to prioritize displaying non-expired milestones and milestones without due dates sorted = reorder(Arel.sql("(CASE WHEN due_date IS NULL THEN 1 WHEN due_date >= CURRENT_DATE THEN 0 ELSE 2 END) ASC")) sorted = if method.to_s == 'expired_last_due_date_desc' sorted.order(due_date: :desc) else sorted.order(due_date: :asc) end sorted.with_order_id_desc end |
.states_count(projects, groups = nil) ⇒ Object
212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'app/models/milestone.rb', line 212 def self.states_count(projects, groups = nil) counts = Milestone .for_projects_and_groups(projects, groups) .reorder(nil) .group(:state) .count { opened: counts['active'] || 0, closed: counts['closed'] || 0, all: counts.values.sum } end |
.upcoming_ids(projects, groups, legacy_filtering_logic: false) ⇒ Object
153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'app/models/milestone.rb', line 153 def self.upcoming_ids(projects, groups, legacy_filtering_logic: false) if legacy_filtering_logic unscoped .for_projects_and_groups(projects, groups) .active.where('milestones.due_date > CURRENT_DATE') .order(:project_id, :group_id, :due_date).select('DISTINCT ON (project_id, group_id) id') else unscoped .for_projects_and_groups(projects, groups) .active.where('milestones.start_date > CURRENT_DATE') .order(:project_id, :group_id, :start_date).select(:id) end end |
.with_web_entity_associations ⇒ Object
167 168 169 |
# File 'app/models/milestone.rb', line 167 def self.with_web_entity_associations preload(:group, project: [:project_feature, { group: [:parent], namespace: :route }]) end |
Instance Method Details
#author_id ⇒ Object
230 231 232 |
# File 'app/models/milestone.rb', line 230 def nil end |
#check_for_spam? ⇒ Boolean
297 298 299 |
# File 'app/models/milestone.rb', line 297 def check_for_spam?(*) spammable_attribute_changed? && parent.public? end |
#for_display ⇒ Object
226 227 228 |
# File 'app/models/milestone.rb', line 226 def for_display self end |
#group_milestone? ⇒ Boolean
238 239 240 |
# File 'app/models/milestone.rb', line 238 def group_milestone? group_id.present? end |
#hook_attrs ⇒ Object
107 108 109 |
# File 'app/models/milestone.rb', line 107 def hook_attrs Gitlab::HookData::MilestoneBuilder.new(self).build end |
#merge_requests_enabled? ⇒ Boolean
262 263 264 265 266 267 268 269 270 |
# File 'app/models/milestone.rb', line 262 def merge_requests_enabled? if group_milestone? # Assume that groups have at least one project with merge requests enabled. # Otherwise, we would need to load all of the projects from the database. true elsif project_milestone? project&.merge_requests_enabled? end end |
#parent ⇒ Object
250 251 252 253 254 255 256 |
# File 'app/models/milestone.rb', line 250 def parent if group_milestone? group else project end end |
#participants ⇒ Object
171 172 173 174 175 |
# File 'app/models/milestone.rb', line 171 def participants User.joins(assigned_issues: :milestone) .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/422155') .where(milestones: { id: id }).distinct end |
#project_milestone? ⇒ Boolean
242 243 244 |
# File 'app/models/milestone.rb', line 242 def project_milestone? project_id.present? end |
#resource_parent ⇒ Object
246 247 248 |
# File 'app/models/milestone.rb', line 246 def resource_parent group || project end |
#subgroup_milestone? ⇒ Boolean
258 259 260 |
# File 'app/models/milestone.rb', line 258 def subgroup_milestone? group_milestone? && parent.subgroup? end |
#to_reference(from = nil, format: :name, full: false, absolute_path: false) ⇒ Object
Returns the String necessary to reference a milestone in Markdown. Group milestones only support name references, and do not support cross-project references.
format - Symbol format to use (default: :iid, optional: :name)
Examples:
Milestone.first.to_reference # => "%1"
Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-foss%1"
Milestone.first
.to_reference(project, full: true, absolute_path: true) # => "/gitlab-org/gitlab-foss%1"
286 287 288 289 290 291 292 293 294 295 |
# File 'app/models/milestone.rb', line 286 def to_reference(from = nil, format: :name, full: false, absolute_path: false) format_reference = timebox_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" if project "#{project.to_reference_base(from, full: full, absolute_path: absolute_path)}#{reference}" else "#{group.to_reference_base(from, full: full, absolute_path: absolute_path)}#{reference}" end end |