Class: Milestone
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Milestone
- Includes:
- 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 ResetOnUnionError
ResetOnUnionError::MAX_RESET_PERIOD
Instance Attribute Summary
Attributes included from Importable
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) ⇒ Object
- .with_web_entity_associations ⇒ Object
Instance Method Summary collapse
- #author_id ⇒ Object
- #can_be_closed? ⇒ Boolean
- #check_for_spam? ⇒ Boolean
- #for_display ⇒ Object
- #group_milestone? ⇒ Boolean
- #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) ⇒ Object
Returns the String necessary to reference a milestone in Markdown.
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, #opened_issues_count, #percent_complete, #remaining_days, #sorted_issues, #sorted_merge_requests, #total_issues_count, #total_merge_requests_count, #total_time_estimate, #total_time_spent, #upcoming?
Methods included from Timebox
#reference_link_text, #safe_title, #timebox_name, #title=, #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, #local_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!, #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, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, 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 SensitiveSerializableHash
Class Method Details
.link_reference_pattern ⇒ Object
121 122 123 |
# File 'app/models/milestone.rb', line 121 def self.link_reference_pattern @link_reference_pattern ||= compose_link_reference_pattern('milestones', /(?<milestone>\d+)/) end |
.min_chars_for_partial_matching ⇒ Object
94 95 96 |
# File 'app/models/milestone.rb', line 94 def self.min_chars_for_partial_matching 2 end |
.reference_pattern ⇒ Object
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'app/models/milestone.rb', line 102 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
98 99 100 |
# File 'app/models/milestone.rb', line 98 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.
90 91 92 |
# File 'app/models/milestone.rb', line 90 def self.search_title(query) fuzzy_search(query, [:title]) end |
.sort_by_attribute(method) ⇒ Object
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'app/models/milestone.rb', line 142 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
164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'app/models/milestone.rb', line 164 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
177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'app/models/milestone.rb', line 177 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) ⇒ Object
125 126 127 128 129 130 |
# File 'app/models/milestone.rb', line 125 def self.upcoming_ids(projects, groups) 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') end |
.with_web_entity_associations ⇒ Object
132 133 134 |
# File 'app/models/milestone.rb', line 132 def self.with_web_entity_associations preload(:group, project: [:project_feature, group: [:parent], namespace: :route]) end |
Instance Method Details
#author_id ⇒ Object
199 200 201 |
# File 'app/models/milestone.rb', line 199 def nil end |
#can_be_closed? ⇒ Boolean
195 196 197 |
# File 'app/models/milestone.rb', line 195 def can_be_closed? active? && issues.opened.count == 0 end |
#check_for_spam? ⇒ Boolean
264 265 266 |
# File 'app/models/milestone.rb', line 264 def check_for_spam?(*) spammable_attribute_changed? && parent.public? end |
#for_display ⇒ Object
191 192 193 |
# File 'app/models/milestone.rb', line 191 def for_display self end |
#group_milestone? ⇒ Boolean
207 208 209 |
# File 'app/models/milestone.rb', line 207 def group_milestone? group_id.present? end |
#merge_requests_enabled? ⇒ Boolean
231 232 233 234 235 236 237 238 239 |
# File 'app/models/milestone.rb', line 231 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
219 220 221 222 223 224 225 |
# File 'app/models/milestone.rb', line 219 def parent if group_milestone? group else project end end |
#participants ⇒ Object
136 137 138 139 140 |
# File 'app/models/milestone.rb', line 136 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
211 212 213 |
# File 'app/models/milestone.rb', line 211 def project_milestone? project_id.present? end |
#resource_parent ⇒ Object
215 216 217 |
# File 'app/models/milestone.rb', line 215 def resource_parent group || project end |
#subgroup_milestone? ⇒ Boolean
227 228 229 |
# File 'app/models/milestone.rb', line 227 def subgroup_milestone? group_milestone? && parent.subgroup? end |
#to_reference(from = nil, format: :name, full: 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"
253 254 255 256 257 258 259 260 261 262 |
# File 'app/models/milestone.rb', line 253 def to_reference(from = nil, format: :name, full: false) format_reference = timebox_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" if project "#{project.to_reference_base(from, full: full)}#{reference}" else reference end end |